diff --git a/app/build.gradle b/app/build.gradle
index 88120c990c..e4360ba73c 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -342,7 +342,9 @@ dependencies {
implementation "io.github.sinaweibosdk:core:${weiboSDK}"
- implementation "com.lg:gid:1.3.0"
+ implementation "com.lg:apksig:${apksig}"
+
+ implementation "com.lg:gid:${gid}"
implementation "com.louiscad.splitties:splitties-fun-pack-android-base-with-views-dsl:${splitties}"
diff --git a/app/src/main/java/com/android/apksig/ApkSigner.java b/app/src/main/java/com/android/apksig/ApkSigner.java
deleted file mode 100644
index ca792c4c6a..0000000000
--- a/app/src/main/java/com/android/apksig/ApkSigner.java
+++ /dev/null
@@ -1,1560 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.apksig;
-
-import static com.android.apksig.apk.ApkUtils.SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME;
-
-import com.android.apksig.apk.ApkFormatException;
-import com.android.apksig.apk.ApkSigningBlockNotFoundException;
-import com.android.apksig.apk.ApkUtils;
-import com.android.apksig.apk.MinSdkVersionException;
-import com.android.apksig.internal.util.ByteBufferDataSource;
-import com.android.apksig.internal.zip.CentralDirectoryRecord;
-import com.android.apksig.internal.zip.EocdRecord;
-import com.android.apksig.internal.zip.LocalFileRecord;
-import com.android.apksig.internal.zip.ZipUtils;
-import com.android.apksig.util.DataSink;
-import com.android.apksig.util.DataSinks;
-import com.android.apksig.util.DataSource;
-import com.android.apksig.util.DataSources;
-import com.android.apksig.util.ReadableDataSink;
-import com.android.apksig.zip.ZipFormatException;
-
-import java.io.Closeable;
-import java.io.File;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.SignatureException;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * APK signer.
- *
- *
The signer preserves as much of the input APK as possible. For example, it preserves the order
- * of APK entries and preserves their contents, including compressed form and alignment of data.
- *
- *
Use {@link Builder} to obtain instances of this signer.
- *
- * @see Application Signing
- */
-public class ApkSigner {
-
- /**
- * Extensible data block/field header ID used for storing information about alignment of
- * uncompressed entries as well as for aligning the entries's data. See ZIP appnote.txt section
- * 4.5 Extensible data fields.
- */
- private static final short ALIGNMENT_ZIP_EXTRA_DATA_FIELD_HEADER_ID = (short) 0xd935;
-
- /**
- * Minimum size (in bytes) of the extensible data block/field used for alignment of uncompressed
- * entries.
- */
- private static final short ALIGNMENT_ZIP_EXTRA_DATA_FIELD_MIN_SIZE_BYTES = 6;
-
- private static final short ANDROID_COMMON_PAGE_ALIGNMENT_BYTES = 4096;
-
- /** Name of the Android manifest ZIP entry in APKs. */
- private static final String ANDROID_MANIFEST_ZIP_ENTRY_NAME = "AndroidManifest.xml";
-
- private final List mSignerConfigs;
- private final SignerConfig mSourceStampSignerConfig;
- private final SigningCertificateLineage mSourceStampSigningCertificateLineage;
- private final boolean mForceSourceStampOverwrite;
- private final Integer mMinSdkVersion;
- private final boolean mV1SigningEnabled;
- private final boolean mV2SigningEnabled;
- private final boolean mV3SigningEnabled;
- private final boolean mV4SigningEnabled;
- private final boolean mVerityEnabled;
- private final boolean mV4ErrorReportingEnabled;
- private final boolean mDebuggableApkPermitted;
- private final boolean mOtherSignersSignaturesPreserved;
- private final String mCreatedBy;
-
- private final ApkSignerEngine mSignerEngine;
-
- private final File mInputApkFile;
- private final DataSource mInputApkDataSource;
-
- private final File mOutputApkFile;
- private final DataSink mOutputApkDataSink;
- private final DataSource mOutputApkDataSource;
-
- private final File mOutputV4File;
-
- private final SigningCertificateLineage mSigningCertificateLineage;
-
- private ApkSigner(
- List signerConfigs,
- SignerConfig sourceStampSignerConfig,
- SigningCertificateLineage sourceStampSigningCertificateLineage,
- boolean forceSourceStampOverwrite,
- Integer minSdkVersion,
- boolean v1SigningEnabled,
- boolean v2SigningEnabled,
- boolean v3SigningEnabled,
- boolean v4SigningEnabled,
- boolean verityEnabled,
- boolean v4ErrorReportingEnabled,
- boolean debuggableApkPermitted,
- boolean otherSignersSignaturesPreserved,
- String createdBy,
- ApkSignerEngine signerEngine,
- File inputApkFile,
- DataSource inputApkDataSource,
- File outputApkFile,
- DataSink outputApkDataSink,
- DataSource outputApkDataSource,
- File outputV4File,
- SigningCertificateLineage signingCertificateLineage) {
-
- mSignerConfigs = signerConfigs;
- mSourceStampSignerConfig = sourceStampSignerConfig;
- mSourceStampSigningCertificateLineage = sourceStampSigningCertificateLineage;
- mForceSourceStampOverwrite = forceSourceStampOverwrite;
- mMinSdkVersion = minSdkVersion;
- mV1SigningEnabled = v1SigningEnabled;
- mV2SigningEnabled = v2SigningEnabled;
- mV3SigningEnabled = v3SigningEnabled;
- mV4SigningEnabled = v4SigningEnabled;
- mVerityEnabled = verityEnabled;
- mV4ErrorReportingEnabled = v4ErrorReportingEnabled;
- mDebuggableApkPermitted = debuggableApkPermitted;
- mOtherSignersSignaturesPreserved = otherSignersSignaturesPreserved;
- mCreatedBy = createdBy;
-
- mSignerEngine = signerEngine;
-
- mInputApkFile = inputApkFile;
- mInputApkDataSource = inputApkDataSource;
-
- mOutputApkFile = outputApkFile;
- mOutputApkDataSink = outputApkDataSink;
- mOutputApkDataSource = outputApkDataSource;
-
- mOutputV4File = outputV4File;
-
- mSigningCertificateLineage = signingCertificateLineage;
- }
-
- /**
- * Signs the input APK and outputs the resulting signed APK. The input APK is not modified.
- *
- * @throws IOException if an I/O error is encountered while reading or writing the APKs
- * @throws ApkFormatException if the input APK is malformed
- * @throws NoSuchAlgorithmException if the APK signatures cannot be produced or verified because
- * a required cryptographic algorithm implementation is missing
- * @throws InvalidKeyException if a signature could not be generated because a signing key is
- * not suitable for generating the signature
- * @throws SignatureException if an error occurred while generating or verifying a signature
- * @throws IllegalStateException if this signer's configuration is missing required information
- * or if the signing engine is in an invalid state.
- */
- public void sign()
- throws IOException, ApkFormatException, NoSuchAlgorithmException, InvalidKeyException,
- SignatureException, IllegalStateException {
- Closeable in = null;
- DataSource inputApk;
- try {
- if (mInputApkDataSource != null) {
- inputApk = mInputApkDataSource;
- } else if (mInputApkFile != null) {
- RandomAccessFile inputFile = new RandomAccessFile(mInputApkFile, "r");
- in = inputFile;
- inputApk = DataSources.asDataSource(inputFile);
- } else {
- throw new IllegalStateException("Input APK not specified");
- }
-
- Closeable out = null;
- try {
- DataSink outputApkOut;
- DataSource outputApkIn;
- if (mOutputApkDataSink != null) {
- outputApkOut = mOutputApkDataSink;
- outputApkIn = mOutputApkDataSource;
- } else if (mOutputApkFile != null) {
- RandomAccessFile outputFile = new RandomAccessFile(mOutputApkFile, "rw");
- out = outputFile;
- outputFile.setLength(0);
- outputApkOut = DataSinks.asDataSink(outputFile);
- outputApkIn = DataSources.asDataSource(outputFile);
- } else {
- throw new IllegalStateException("Output APK not specified");
- }
-
- sign(inputApk, outputApkOut, outputApkIn);
- } finally {
- if (out != null) {
- out.close();
- }
- }
- } finally {
- if (in != null) {
- in.close();
- }
- }
- }
-
- private void sign(DataSource inputApk, DataSink outputApkOut, DataSource outputApkIn)
- throws IOException, ApkFormatException, NoSuchAlgorithmException, InvalidKeyException,
- SignatureException {
- // Step 1. Find input APK's main ZIP sections
- ApkUtils.ZipSections inputZipSections;
- try {
- inputZipSections = ApkUtils.findZipSections(inputApk);
- } catch (ZipFormatException e) {
- throw new ApkFormatException("Malformed APK: not a ZIP archive", e);
- }
- long inputApkSigningBlockOffset = -1;
- DataSource inputApkSigningBlock = null;
- try {
- ApkUtils.ApkSigningBlock apkSigningBlockInfo =
- ApkUtils.findApkSigningBlock(inputApk, inputZipSections);
- inputApkSigningBlockOffset = apkSigningBlockInfo.getStartOffset();
- inputApkSigningBlock = apkSigningBlockInfo.getContents();
- } catch (ApkSigningBlockNotFoundException e) {
- // Input APK does not contain an APK Signing Block. That's OK. APKs are not required to
- // contain this block. It's only needed if the APK is signed using APK Signature Scheme
- // v2 and/or v3.
- }
- DataSource inputApkLfhSection =
- inputApk.slice(
- 0,
- (inputApkSigningBlockOffset != -1)
- ? inputApkSigningBlockOffset
- : inputZipSections.getZipCentralDirectoryOffset());
-
- // Step 2. Parse the input APK's ZIP Central Directory
- ByteBuffer inputCd = getZipCentralDirectory(inputApk, inputZipSections);
- List inputCdRecords =
- parseZipCentralDirectory(inputCd, inputZipSections);
-
- List pinPatterns =
- extractPinPatterns(inputCdRecords, inputApkLfhSection);
- List pinByteRanges = pinPatterns == null ? null : new ArrayList<>();
-
- // Step 3. Obtain a signer engine instance
- ApkSignerEngine signerEngine;
- if (mSignerEngine != null) {
- // Use the provided signer engine
- signerEngine = mSignerEngine;
- } else {
- // Construct a signer engine from the provided parameters
- int minSdkVersion;
- if (mMinSdkVersion != null) {
- // No need to extract minSdkVersion from the APK's AndroidManifest.xml
- minSdkVersion = mMinSdkVersion;
- } else {
- // Need to extract minSdkVersion from the APK's AndroidManifest.xml
- minSdkVersion = getMinSdkVersionFromApk(inputCdRecords, inputApkLfhSection);
- }
- List engineSignerConfigs =
- new ArrayList<>(mSignerConfigs.size());
- for (SignerConfig signerConfig : mSignerConfigs) {
- engineSignerConfigs.add(
- new DefaultApkSignerEngine.SignerConfig.Builder(
- signerConfig.getName(),
- signerConfig.getPrivateKey(),
- signerConfig.getCertificates(),
- signerConfig.getDeterministicDsaSigning())
- .build());
- }
- DefaultApkSignerEngine.Builder signerEngineBuilder =
- new DefaultApkSignerEngine.Builder(engineSignerConfigs, minSdkVersion)
- .setV1SigningEnabled(mV1SigningEnabled)
- .setV2SigningEnabled(mV2SigningEnabled)
- .setV3SigningEnabled(mV3SigningEnabled)
- .setVerityEnabled(mVerityEnabled)
- .setDebuggableApkPermitted(mDebuggableApkPermitted)
- .setOtherSignersSignaturesPreserved(mOtherSignersSignaturesPreserved)
- .setSigningCertificateLineage(mSigningCertificateLineage);
- if (mCreatedBy != null) {
- signerEngineBuilder.setCreatedBy(mCreatedBy);
- }
- if (mSourceStampSignerConfig != null) {
- signerEngineBuilder.setStampSignerConfig(
- new DefaultApkSignerEngine.SignerConfig.Builder(
- mSourceStampSignerConfig.getName(),
- mSourceStampSignerConfig.getPrivateKey(),
- mSourceStampSignerConfig.getCertificates(),
- mSourceStampSignerConfig.getDeterministicDsaSigning())
- .build());
- }
- if (mSourceStampSigningCertificateLineage != null) {
- signerEngineBuilder.setSourceStampSigningCertificateLineage(
- mSourceStampSigningCertificateLineage);
- }
- signerEngine = signerEngineBuilder.build();
- }
-
- // Step 4. Provide the signer engine with the input APK's APK Signing Block (if any)
- if (inputApkSigningBlock != null) {
- signerEngine.inputApkSigningBlock(inputApkSigningBlock);
- }
-
- // Step 5. Iterate over input APK's entries and output the Local File Header + data of those
- // entries which need to be output. Entries are iterated in the order in which their Local
- // File Header records are stored in the file. This is to achieve better data locality in
- // case Central Directory entries are in the wrong order.
- List inputCdRecordsSortedByLfhOffset =
- new ArrayList<>(inputCdRecords);
- Collections.sort(
- inputCdRecordsSortedByLfhOffset,
- CentralDirectoryRecord.BY_LOCAL_FILE_HEADER_OFFSET_COMPARATOR);
- int lastModifiedDateForNewEntries = -1;
- int lastModifiedTimeForNewEntries = -1;
- long inputOffset = 0;
- long outputOffset = 0;
- byte[] sourceStampCertificateDigest = null;
- Map outputCdRecordsByName =
- new HashMap<>(inputCdRecords.size());
- for (final CentralDirectoryRecord inputCdRecord : inputCdRecordsSortedByLfhOffset) {
- String entryName = inputCdRecord.getName();
- if (Hints.PIN_BYTE_RANGE_ZIP_ENTRY_NAME.equals(entryName)) {
- continue; // We'll re-add below if needed.
- }
- if (SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME.equals(entryName)) {
- try {
- sourceStampCertificateDigest =
- LocalFileRecord.getUncompressedData(
- inputApkLfhSection, inputCdRecord, inputApkLfhSection.size());
- } catch (ZipFormatException ex) {
- throw new ApkFormatException("Bad source stamp entry");
- }
- continue; // Existing source stamp is handled below as needed.
- }
- ApkSignerEngine.InputJarEntryInstructions entryInstructions =
- signerEngine.inputJarEntry(entryName);
- boolean shouldOutput;
- switch (entryInstructions.getOutputPolicy()) {
- case OUTPUT:
- shouldOutput = true;
- break;
- case OUTPUT_BY_ENGINE:
- case SKIP:
- shouldOutput = false;
- break;
- default:
- throw new RuntimeException(
- "Unknown output policy: " + entryInstructions.getOutputPolicy());
- }
-
- long inputLocalFileHeaderStartOffset = inputCdRecord.getLocalFileHeaderOffset();
- if (inputLocalFileHeaderStartOffset > inputOffset) {
- // Unprocessed data in input starting at inputOffset and ending and the start of
- // this record's LFH. We output this data verbatim because this signer is supposed
- // to preserve as much of input as possible.
- long chunkSize = inputLocalFileHeaderStartOffset - inputOffset;
- inputApkLfhSection.feed(inputOffset, chunkSize, outputApkOut);
- outputOffset += chunkSize;
- inputOffset = inputLocalFileHeaderStartOffset;
- }
- LocalFileRecord inputLocalFileRecord;
- try {
- inputLocalFileRecord =
- LocalFileRecord.getRecord(
- inputApkLfhSection, inputCdRecord, inputApkLfhSection.size());
- } catch (ZipFormatException e) {
- throw new ApkFormatException("Malformed ZIP entry: " + inputCdRecord.getName(), e);
- }
- inputOffset += inputLocalFileRecord.getSize();
-
- ApkSignerEngine.InspectJarEntryRequest inspectEntryRequest =
- entryInstructions.getInspectJarEntryRequest();
- if (inspectEntryRequest != null) {
- fulfillInspectInputJarEntryRequest(
- inputApkLfhSection, inputLocalFileRecord, inspectEntryRequest);
- }
-
- if (shouldOutput) {
- // Find the max value of last modified, to be used for new entries added by the
- // signer.
- int lastModifiedDate = inputCdRecord.getLastModificationDate();
- int lastModifiedTime = inputCdRecord.getLastModificationTime();
- if ((lastModifiedDateForNewEntries == -1)
- || (lastModifiedDate > lastModifiedDateForNewEntries)
- || ((lastModifiedDate == lastModifiedDateForNewEntries)
- && (lastModifiedTime > lastModifiedTimeForNewEntries))) {
- lastModifiedDateForNewEntries = lastModifiedDate;
- lastModifiedTimeForNewEntries = lastModifiedTime;
- }
-
- inspectEntryRequest = signerEngine.outputJarEntry(entryName);
- if (inspectEntryRequest != null) {
- fulfillInspectInputJarEntryRequest(
- inputApkLfhSection, inputLocalFileRecord, inspectEntryRequest);
- }
-
- // Output entry's Local File Header + data
- long outputLocalFileHeaderOffset = outputOffset;
- OutputSizeAndDataOffset outputLfrResult =
- outputInputJarEntryLfhRecordPreservingDataAlignment(
- inputApkLfhSection,
- inputLocalFileRecord,
- outputApkOut,
- outputLocalFileHeaderOffset);
- outputOffset += outputLfrResult.outputBytes;
- long outputDataOffset =
- outputLocalFileHeaderOffset + outputLfrResult.dataOffsetBytes;
-
- if (pinPatterns != null) {
- boolean pinFileHeader = false;
- for (Hints.PatternWithRange pinPattern : pinPatterns) {
- if (pinPattern.matcher(inputCdRecord.getName()).matches()) {
- Hints.ByteRange dataRange =
- new Hints.ByteRange(outputDataOffset, outputOffset);
- Hints.ByteRange pinRange =
- pinPattern.ClampToAbsoluteByteRange(dataRange);
- if (pinRange != null) {
- pinFileHeader = true;
- pinByteRanges.add(pinRange);
- }
- }
- }
- if (pinFileHeader) {
- pinByteRanges.add(
- new Hints.ByteRange(outputLocalFileHeaderOffset, outputDataOffset));
- }
- }
-
- // Enqueue entry's Central Directory record for output
- CentralDirectoryRecord outputCdRecord;
- if (outputLocalFileHeaderOffset == inputLocalFileRecord.getStartOffsetInArchive()) {
- outputCdRecord = inputCdRecord;
- } else {
- outputCdRecord =
- inputCdRecord.createWithModifiedLocalFileHeaderOffset(
- outputLocalFileHeaderOffset);
- }
- outputCdRecordsByName.put(entryName, outputCdRecord);
- }
- }
- long inputLfhSectionSize = inputApkLfhSection.size();
- if (inputOffset < inputLfhSectionSize) {
- // Unprocessed data in input starting at inputOffset and ending and the end of the input
- // APK's LFH section. We output this data verbatim because this signer is supposed
- // to preserve as much of input as possible.
- long chunkSize = inputLfhSectionSize - inputOffset;
- inputApkLfhSection.feed(inputOffset, chunkSize, outputApkOut);
- outputOffset += chunkSize;
- inputOffset = inputLfhSectionSize;
- }
-
- // Step 6. Sort output APK's Central Directory records in the order in which they should
- // appear in the output
- List outputCdRecords = new ArrayList<>(inputCdRecords.size() + 10);
- for (CentralDirectoryRecord inputCdRecord : inputCdRecords) {
- String entryName = inputCdRecord.getName();
- CentralDirectoryRecord outputCdRecord = outputCdRecordsByName.get(entryName);
- if (outputCdRecord != null) {
- outputCdRecords.add(outputCdRecord);
- }
- }
-
- if (lastModifiedDateForNewEntries == -1) {
- lastModifiedDateForNewEntries = 0x3a21; // Jan 1 2009 (DOS)
- lastModifiedTimeForNewEntries = 0;
- }
-
- // Step 7. Generate and output SourceStamp certificate hash, if necessary. This may output
- // more Local File Header + data entries and add to the list of output Central Directory
- // records.
- if (signerEngine.isEligibleForSourceStamp()) {
- byte[] uncompressedData = signerEngine.generateSourceStampCertificateDigest();
- if (mForceSourceStampOverwrite
- || sourceStampCertificateDigest == null
- || Arrays.equals(uncompressedData, sourceStampCertificateDigest)) {
- outputOffset +=
- outputDataToOutputApk(
- SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME,
- uncompressedData,
- outputOffset,
- outputCdRecords,
- lastModifiedTimeForNewEntries,
- lastModifiedDateForNewEntries,
- outputApkOut);
- } else {
- throw new ApkFormatException(
- String.format(
- "Cannot generate SourceStamp. APK contains an existing entry with"
- + " the name: %s, and it is different than the provided source"
- + " stamp certificate",
- SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME));
- }
- }
-
- // Step 7.5. Generate pinlist.meta file if necessary.
- // This has to be before the step 8 so that the file is signed.
- if (pinByteRanges != null) {
- // Covers JAR signature and zip central dir entry.
- // The signature files don't have to be pinned, but pinning them isn't that wasteful
- // since the total size is small.
- pinByteRanges.add(new Hints.ByteRange(outputOffset, Long.MAX_VALUE));
- String entryName = Hints.PIN_BYTE_RANGE_ZIP_ENTRY_NAME;
- byte[] uncompressedData = Hints.encodeByteRangeList(pinByteRanges);
-
- requestOutputEntryInspection(signerEngine, entryName, uncompressedData);
- outputOffset +=
- outputDataToOutputApk(
- entryName,
- uncompressedData,
- outputOffset,
- outputCdRecords,
- lastModifiedTimeForNewEntries,
- lastModifiedDateForNewEntries,
- outputApkOut);
- }
-
- // Step 8. Generate and output JAR signatures, if necessary. This may output more Local File
- // Header + data entries and add to the list of output Central Directory records.
- ApkSignerEngine.OutputJarSignatureRequest outputJarSignatureRequest =
- signerEngine.outputJarEntries();
- if (outputJarSignatureRequest != null) {
- for (ApkSignerEngine.OutputJarSignatureRequest.JarEntry entry :
- outputJarSignatureRequest.getAdditionalJarEntries()) {
- String entryName = entry.getName();
- byte[] uncompressedData = entry.getData();
-
- requestOutputEntryInspection(signerEngine, entryName, uncompressedData);
- outputOffset +=
- outputDataToOutputApk(
- entryName,
- uncompressedData,
- outputOffset,
- outputCdRecords,
- lastModifiedTimeForNewEntries,
- lastModifiedDateForNewEntries,
- outputApkOut);
- }
- outputJarSignatureRequest.done();
- }
-
- // Step 9. Construct output ZIP Central Directory in an in-memory buffer
- long outputCentralDirSizeBytes = 0;
- for (CentralDirectoryRecord record : outputCdRecords) {
- outputCentralDirSizeBytes += record.getSize();
- }
- if (outputCentralDirSizeBytes > Integer.MAX_VALUE) {
- throw new IOException(
- "Output ZIP Central Directory too large: "
- + outputCentralDirSizeBytes
- + " bytes");
- }
- ByteBuffer outputCentralDir = ByteBuffer.allocate((int) outputCentralDirSizeBytes);
- for (CentralDirectoryRecord record : outputCdRecords) {
- record.copyTo(outputCentralDir);
- }
- outputCentralDir.flip();
- DataSource outputCentralDirDataSource = new ByteBufferDataSource(outputCentralDir);
- long outputCentralDirStartOffset = outputOffset;
- int outputCentralDirRecordCount = outputCdRecords.size();
-
- // Step 10. Construct output ZIP End of Central Directory record in an in-memory buffer
- ByteBuffer outputEocd =
- EocdRecord.createWithModifiedCentralDirectoryInfo(
- inputZipSections.getZipEndOfCentralDirectory(),
- outputCentralDirRecordCount,
- outputCentralDirDataSource.size(),
- outputCentralDirStartOffset);
-
- // Step 11. Generate and output APK Signature Scheme v2 and/or v3 signatures and/or
- // SourceStamp signatures, if necessary.
- // This may insert an APK Signing Block just before the output's ZIP Central Directory
- ApkSignerEngine.OutputApkSigningBlockRequest2 outputApkSigningBlockRequest =
- signerEngine.outputZipSections2(
- outputApkIn,
- outputCentralDirDataSource,
- DataSources.asDataSource(outputEocd));
-
- if (outputApkSigningBlockRequest != null) {
- int padding = outputApkSigningBlockRequest.getPaddingSizeBeforeApkSigningBlock();
- outputApkOut.consume(ByteBuffer.allocate(padding));
- byte[] outputApkSigningBlock = outputApkSigningBlockRequest.getApkSigningBlock();
- outputApkOut.consume(outputApkSigningBlock, 0, outputApkSigningBlock.length);
- ZipUtils.setZipEocdCentralDirectoryOffset(
- outputEocd,
- outputCentralDirStartOffset + padding + outputApkSigningBlock.length);
- outputApkSigningBlockRequest.done();
- }
-
- // Step 12. Output ZIP Central Directory and ZIP End of Central Directory
- outputCentralDirDataSource.feed(0, outputCentralDirDataSource.size(), outputApkOut);
- outputApkOut.consume(outputEocd);
- signerEngine.outputDone();
-
- // Step 13. Generate and output APK Signature Scheme v4 signatures, if necessary.
- if (mV4SigningEnabled) {
- signerEngine.signV4(outputApkIn, mOutputV4File, !mV4ErrorReportingEnabled);
- }
- }
-
- private static void requestOutputEntryInspection(
- ApkSignerEngine signerEngine,
- String entryName,
- byte[] uncompressedData)
- throws IOException {
- ApkSignerEngine.InspectJarEntryRequest inspectEntryRequest =
- signerEngine.outputJarEntry(entryName);
- if (inspectEntryRequest != null) {
- inspectEntryRequest.getDataSink().consume(
- uncompressedData, 0, uncompressedData.length);
- inspectEntryRequest.done();
- }
- }
-
- private static long outputDataToOutputApk(
- String entryName,
- byte[] uncompressedData,
- long localFileHeaderOffset,
- List outputCdRecords,
- int lastModifiedTimeForNewEntries,
- int lastModifiedDateForNewEntries,
- DataSink outputApkOut)
- throws IOException {
- ZipUtils.DeflateResult deflateResult = ZipUtils.deflate(ByteBuffer.wrap(uncompressedData));
- byte[] compressedData = deflateResult.output;
- long uncompressedDataCrc32 = deflateResult.inputCrc32;
- long numOfDataBytes =
- LocalFileRecord.outputRecordWithDeflateCompressedData(
- entryName,
- lastModifiedTimeForNewEntries,
- lastModifiedDateForNewEntries,
- compressedData,
- uncompressedDataCrc32,
- uncompressedData.length,
- outputApkOut);
- outputCdRecords.add(
- CentralDirectoryRecord.createWithDeflateCompressedData(
- entryName,
- lastModifiedTimeForNewEntries,
- lastModifiedDateForNewEntries,
- uncompressedDataCrc32,
- compressedData.length,
- uncompressedData.length,
- localFileHeaderOffset));
- return numOfDataBytes;
- }
-
- private static void fulfillInspectInputJarEntryRequest(
- DataSource lfhSection,
- LocalFileRecord localFileRecord,
- ApkSignerEngine.InspectJarEntryRequest inspectEntryRequest)
- throws IOException, ApkFormatException {
- try {
- localFileRecord.outputUncompressedData(lfhSection, inspectEntryRequest.getDataSink());
- } catch (ZipFormatException e) {
- throw new ApkFormatException("Malformed ZIP entry: " + localFileRecord.getName(), e);
- }
- inspectEntryRequest.done();
- }
-
- private static class OutputSizeAndDataOffset {
- public long outputBytes;
- public long dataOffsetBytes;
-
- public OutputSizeAndDataOffset(long outputBytes, long dataOffsetBytes) {
- this.outputBytes = outputBytes;
- this.dataOffsetBytes = dataOffsetBytes;
- }
- }
-
- private static OutputSizeAndDataOffset outputInputJarEntryLfhRecordPreservingDataAlignment(
- DataSource inputLfhSection,
- LocalFileRecord inputRecord,
- DataSink outputLfhSection,
- long outputOffset)
- throws IOException {
- long inputOffset = inputRecord.getStartOffsetInArchive();
- if (inputOffset == outputOffset) {
- // This record's data will be aligned same as in the input APK.
- return new OutputSizeAndDataOffset(
- inputRecord.outputRecord(inputLfhSection, outputLfhSection),
- inputRecord.getDataStartOffsetInRecord());
- }
- int dataAlignmentMultiple = getInputJarEntryDataAlignmentMultiple(inputRecord);
- if ((dataAlignmentMultiple <= 1)
- || ((inputOffset % dataAlignmentMultiple)
- == (outputOffset % dataAlignmentMultiple))) {
- // This record's data will be aligned same as in the input APK.
- return new OutputSizeAndDataOffset(
- inputRecord.outputRecord(inputLfhSection, outputLfhSection),
- inputRecord.getDataStartOffsetInRecord());
- }
-
- long inputDataStartOffset = inputOffset + inputRecord.getDataStartOffsetInRecord();
- if ((inputDataStartOffset % dataAlignmentMultiple) != 0) {
- // This record's data is not aligned in the input APK. No need to align it in the
- // output.
- return new OutputSizeAndDataOffset(
- inputRecord.outputRecord(inputLfhSection, outputLfhSection),
- inputRecord.getDataStartOffsetInRecord());
- }
-
- // This record's data needs to be re-aligned in the output. This is achieved using the
- // record's extra field.
- ByteBuffer aligningExtra =
- createExtraFieldToAlignData(
- inputRecord.getExtra(),
- outputOffset + inputRecord.getExtraFieldStartOffsetInsideRecord(),
- dataAlignmentMultiple);
- long dataOffset =
- (long) inputRecord.getDataStartOffsetInRecord()
- + aligningExtra.remaining()
- - inputRecord.getExtra().remaining();
- return new OutputSizeAndDataOffset(
- inputRecord.outputRecordWithModifiedExtra(
- inputLfhSection, aligningExtra, outputLfhSection),
- dataOffset);
- }
-
- private static int getInputJarEntryDataAlignmentMultiple(LocalFileRecord entry) {
- if (entry.isDataCompressed()) {
- // Compressed entries don't need to be aligned
- return 1;
- }
-
- // Attempt to obtain the alignment multiple from the entry's extra field.
- ByteBuffer extra = entry.getExtra();
- if (extra.hasRemaining()) {
- extra.order(ByteOrder.LITTLE_ENDIAN);
- // FORMAT: sequence of fields. Each field consists of:
- // * uint16 ID
- // * uint16 size
- // * 'size' bytes: payload
- while (extra.remaining() >= 4) {
- short headerId = extra.getShort();
- int dataSize = ZipUtils.getUnsignedInt16(extra);
- if (dataSize > extra.remaining()) {
- // Malformed field -- insufficient input remaining
- break;
- }
- if (headerId != ALIGNMENT_ZIP_EXTRA_DATA_FIELD_HEADER_ID) {
- // Skip this field
- extra.position(extra.position() + dataSize);
- continue;
- }
- // This is APK alignment field.
- // FORMAT:
- // * uint16 alignment multiple (in bytes)
- // * remaining bytes -- padding to achieve alignment of data which starts after
- // the extra field
- if (dataSize < 2) {
- // Malformed
- break;
- }
- return ZipUtils.getUnsignedInt16(extra);
- }
- }
-
- // Fall back to filename-based defaults
- return (entry.getName().endsWith(".so")) ? ANDROID_COMMON_PAGE_ALIGNMENT_BYTES : 4;
- }
-
- private static ByteBuffer createExtraFieldToAlignData(
- ByteBuffer original, long extraStartOffset, int dataAlignmentMultiple) {
- if (dataAlignmentMultiple <= 1) {
- return original;
- }
-
- // In the worst case scenario, we'll increase the output size by 6 + dataAlignment - 1.
- ByteBuffer result = ByteBuffer.allocate(original.remaining() + 5 + dataAlignmentMultiple);
- result.order(ByteOrder.LITTLE_ENDIAN);
-
- // Step 1. Output all extra fields other than the one which is to do with alignment
- // FORMAT: sequence of fields. Each field consists of:
- // * uint16 ID
- // * uint16 size
- // * 'size' bytes: payload
- while (original.remaining() >= 4) {
- short headerId = original.getShort();
- int dataSize = ZipUtils.getUnsignedInt16(original);
- if (dataSize > original.remaining()) {
- // Malformed field -- insufficient input remaining
- break;
- }
- if (((headerId == 0) && (dataSize == 0))
- || (headerId == ALIGNMENT_ZIP_EXTRA_DATA_FIELD_HEADER_ID)) {
- // Ignore the field if it has to do with the old APK data alignment method (filling
- // the extra field with 0x00 bytes) or the new APK data alignment method.
- original.position(original.position() + dataSize);
- continue;
- }
- // Copy this field (including header) to the output
- original.position(original.position() - 4);
- int originalLimit = original.limit();
- original.limit(original.position() + 4 + dataSize);
- result.put(original);
- original.limit(originalLimit);
- }
-
- // Step 2. Add alignment field
- // FORMAT:
- // * uint16 extra header ID
- // * uint16 extra data size
- // Payload ('data size' bytes)
- // * uint16 alignment multiple (in bytes)
- // * remaining bytes -- padding to achieve alignment of data which starts after the
- // extra field
- long dataMinStartOffset =
- extraStartOffset
- + result.position()
- + ALIGNMENT_ZIP_EXTRA_DATA_FIELD_MIN_SIZE_BYTES;
- int paddingSizeBytes =
- (dataAlignmentMultiple - ((int) (dataMinStartOffset % dataAlignmentMultiple)))
- % dataAlignmentMultiple;
- result.putShort(ALIGNMENT_ZIP_EXTRA_DATA_FIELD_HEADER_ID);
- ZipUtils.putUnsignedInt16(result, 2 + paddingSizeBytes);
- ZipUtils.putUnsignedInt16(result, dataAlignmentMultiple);
- result.position(result.position() + paddingSizeBytes);
- result.flip();
-
- return result;
- }
-
- private static ByteBuffer getZipCentralDirectory(
- DataSource apk, ApkUtils.ZipSections apkSections)
- throws IOException, ApkFormatException {
- long cdSizeBytes = apkSections.getZipCentralDirectorySizeBytes();
- if (cdSizeBytes > Integer.MAX_VALUE) {
- throw new ApkFormatException("ZIP Central Directory too large: " + cdSizeBytes);
- }
- long cdOffset = apkSections.getZipCentralDirectoryOffset();
- ByteBuffer cd = apk.getByteBuffer(cdOffset, (int) cdSizeBytes);
- cd.order(ByteOrder.LITTLE_ENDIAN);
- return cd;
- }
-
- private static List parseZipCentralDirectory(
- ByteBuffer cd, ApkUtils.ZipSections apkSections) throws ApkFormatException {
- long cdOffset = apkSections.getZipCentralDirectoryOffset();
- int expectedCdRecordCount = apkSections.getZipCentralDirectoryRecordCount();
- List cdRecords = new ArrayList<>(expectedCdRecordCount);
- Set entryNames = new HashSet<>(expectedCdRecordCount);
- for (int i = 0; i < expectedCdRecordCount; i++) {
- CentralDirectoryRecord cdRecord;
- int offsetInsideCd = cd.position();
- try {
- cdRecord = CentralDirectoryRecord.getRecord(cd);
- } catch (ZipFormatException e) {
- throw new ApkFormatException(
- "Malformed ZIP Central Directory record #"
- + (i + 1)
- + " at file offset "
- + (cdOffset + offsetInsideCd),
- e);
- }
- String entryName = cdRecord.getName();
- if (!entryNames.add(entryName)) {
- throw new ApkFormatException(
- "Multiple ZIP entries with the same name: " + entryName);
- }
- cdRecords.add(cdRecord);
- }
- if (cd.hasRemaining()) {
- throw new ApkFormatException(
- "Unused space at the end of ZIP Central Directory: "
- + cd.remaining()
- + " bytes starting at file offset "
- + (cdOffset + cd.position()));
- }
-
- return cdRecords;
- }
-
- private static CentralDirectoryRecord findCdRecord(
- List cdRecords, String name) {
- for (CentralDirectoryRecord cdRecord : cdRecords) {
- if (name.equals(cdRecord.getName())) {
- return cdRecord;
- }
- }
- return null;
- }
-
- /**
- * Returns the contents of the APK's {@code AndroidManifest.xml} or {@code null} if this entry
- * is not present in the APK.
- */
- static ByteBuffer getAndroidManifestFromApk(
- List cdRecords, DataSource lhfSection)
- throws IOException, ApkFormatException, ZipFormatException {
- CentralDirectoryRecord androidManifestCdRecord =
- findCdRecord(cdRecords, ANDROID_MANIFEST_ZIP_ENTRY_NAME);
- if (androidManifestCdRecord == null) {
- throw new ApkFormatException("Missing " + ANDROID_MANIFEST_ZIP_ENTRY_NAME);
- }
-
- return ByteBuffer.wrap(
- LocalFileRecord.getUncompressedData(
- lhfSection, androidManifestCdRecord, lhfSection.size()));
- }
-
- /**
- * Return list of pin patterns embedded in the pin pattern asset file. If no such file, return
- * {@code null}.
- */
- private static List extractPinPatterns(
- List cdRecords, DataSource lhfSection)
- throws IOException, ApkFormatException {
- CentralDirectoryRecord pinListCdRecord =
- findCdRecord(cdRecords, Hints.PIN_HINT_ASSET_ZIP_ENTRY_NAME);
- List pinPatterns = null;
- if (pinListCdRecord != null) {
- pinPatterns = new ArrayList<>();
- byte[] patternBlob;
- try {
- patternBlob =
- LocalFileRecord.getUncompressedData(
- lhfSection, pinListCdRecord, lhfSection.size());
- } catch (ZipFormatException ex) {
- throw new ApkFormatException("Bad " + pinListCdRecord);
- }
- pinPatterns = Hints.parsePinPatterns(patternBlob);
- }
- return pinPatterns;
- }
-
- /**
- * Returns the minimum Android version (API Level) supported by the provided APK. This is based
- * on the {@code android:minSdkVersion} attributes of the APK's {@code AndroidManifest.xml}.
- */
- private static int getMinSdkVersionFromApk(
- List cdRecords, DataSource lhfSection)
- throws IOException, MinSdkVersionException {
- ByteBuffer androidManifest;
- try {
- androidManifest = getAndroidManifestFromApk(cdRecords, lhfSection);
- } catch (ZipFormatException | ApkFormatException e) {
- throw new MinSdkVersionException(
- "Failed to determine APK's minimum supported Android platform version", e);
- }
- return ApkUtils.getMinSdkVersionFromBinaryAndroidManifest(androidManifest);
- }
-
- /**
- * Configuration of a signer.
- *
- * Use {@link Builder} to obtain configuration instances.
- */
- public static class SignerConfig {
- private final String mName;
- private final PrivateKey mPrivateKey;
- private final List mCertificates;
- private boolean mDeterministicDsaSigning;
-
- private SignerConfig(
- String name,
- PrivateKey privateKey,
- List certificates,
- boolean deterministicDsaSigning) {
- mName = name;
- mPrivateKey = privateKey;
- mCertificates = Collections.unmodifiableList(new ArrayList<>(certificates));
- mDeterministicDsaSigning = deterministicDsaSigning;
- }
- /** Returns the name of this signer. */
- public String getName() {
- return mName;
- }
-
- /** Returns the signing key of this signer. */
- public PrivateKey getPrivateKey() {
- return mPrivateKey;
- }
-
- /**
- * Returns the certificate(s) of this signer. The first certificate's public key corresponds
- * to this signer's private key.
- */
- public List getCertificates() {
- return mCertificates;
- }
-
-
- /**
- * If this signer is a DSA signer, whether or not the signing is done deterministically.
- */
- public boolean getDeterministicDsaSigning() {
- return mDeterministicDsaSigning;
- }
-
- /** Builder of {@link SignerConfig} instances. */
- public static class Builder {
- private final String mName;
- private final PrivateKey mPrivateKey;
- private final List mCertificates;
- private final boolean mDeterministicDsaSigning;
-
- /**
- * Constructs a new {@code Builder}.
- *
- * @param name signer's name. The name is reflected in the name of files comprising the
- * JAR signature of the APK.
- * @param privateKey signing key
- * @param certificates list of one or more X.509 certificates. The subject public key of
- * the first certificate must correspond to the {@code privateKey}.
- */
- public Builder(
- String name,
- PrivateKey privateKey,
- List certificates) {
- this(name, privateKey, certificates, false);
- }
-
- /**
- * Constructs a new {@code Builder}.
- *
- * @param name signer's name. The name is reflected in the name of files comprising the
- * JAR signature of the APK.
- * @param privateKey signing key
- * @param certificates list of one or more X.509 certificates. The subject public key of
- * the first certificate must correspond to the {@code privateKey}.
- * @param deterministicDsaSigning When signing using DSA, whether or not the
- * deterministic variant (RFC6979) should be used.
- */
- public Builder(
- String name,
- PrivateKey privateKey,
- List certificates,
- boolean deterministicDsaSigning) {
- if (name.isEmpty()) {
- throw new IllegalArgumentException("Empty name");
- }
- mName = name;
- mPrivateKey = privateKey;
- mCertificates = new ArrayList<>(certificates);
- mDeterministicDsaSigning = deterministicDsaSigning;
- }
-
- /**
- * Returns a new {@code SignerConfig} instance configured based on the configuration of
- * this builder.
- */
- public SignerConfig build() {
- return new SignerConfig(mName, mPrivateKey, mCertificates,
- mDeterministicDsaSigning);
- }
- }
- }
-
- /**
- * Builder of {@link ApkSigner} instances.
- *
- * The builder requires the following information to construct a working {@code ApkSigner}:
- *
- *
- * - Signer configs or {@link ApkSignerEngine} -- provided in the constructor,
- *
- APK to be signed -- see {@link #setInputApk(File) setInputApk} variants,
- *
- where to store the output signed APK -- see {@link #setOutputApk(File) setOutputApk}
- * variants.
- *
- */
- public static class Builder {
- private final List mSignerConfigs;
- private SignerConfig mSourceStampSignerConfig;
- private SigningCertificateLineage mSourceStampSigningCertificateLineage;
- private boolean mForceSourceStampOverwrite = false;
- private boolean mV1SigningEnabled = true;
- private boolean mV2SigningEnabled = true;
- private boolean mV3SigningEnabled = true;
- private boolean mV4SigningEnabled = true;
- private boolean mVerityEnabled = false;
- private boolean mV4ErrorReportingEnabled = false;
- private boolean mDebuggableApkPermitted = true;
- private boolean mOtherSignersSignaturesPreserved;
- private String mCreatedBy;
- private Integer mMinSdkVersion;
-
- private final ApkSignerEngine mSignerEngine;
-
- private File mInputApkFile;
- private DataSource mInputApkDataSource;
-
- private File mOutputApkFile;
- private DataSink mOutputApkDataSink;
- private DataSource mOutputApkDataSource;
-
- private File mOutputV4File;
-
- private SigningCertificateLineage mSigningCertificateLineage;
-
- // APK Signature Scheme v3 only supports a single signing certificate, so to move to v3
- // signing by default, but not require prior clients to update to explicitly disable v3
- // signing for multiple signers, we modify the mV3SigningEnabled depending on the provided
- // inputs (multiple signers and mSigningCertificateLineage in particular). Maintain two
- // extra variables to record whether or not mV3SigningEnabled has been set directly by a
- // client and so should override the default behavior.
- private boolean mV3SigningExplicitlyDisabled = false;
- private boolean mV3SigningExplicitlyEnabled = false;
-
- /**
- * Constructs a new {@code Builder} for an {@code ApkSigner} which signs using the provided
- * signer configurations. The resulting signer may be further customized through this
- * builder's setters, such as {@link #setMinSdkVersion(int)}, {@link
- * #setV1SigningEnabled(boolean)}, {@link #setV2SigningEnabled(boolean)}, {@link
- * #setOtherSignersSignaturesPreserved(boolean)}, {@link #setCreatedBy(String)}.
- *
- * {@link #Builder(ApkSignerEngine)} is an alternative for advanced use cases where more
- * control over low-level details of signing is desired.
- */
- public Builder(List signerConfigs) {
- if (signerConfigs.isEmpty()) {
- throw new IllegalArgumentException("At least one signer config must be provided");
- }
- if (signerConfigs.size() > 1) {
- // APK Signature Scheme v3 only supports single signer, unless a
- // SigningCertificateLineage is provided, in which case this will be reset to true,
- // since we don't yet have a v4 scheme about which to worry
- mV3SigningEnabled = false;
- }
- mSignerConfigs = new ArrayList<>(signerConfigs);
- mSignerEngine = null;
- }
-
- /**
- * Constructs a new {@code Builder} for an {@code ApkSigner} which signs using the provided
- * signing engine. This is meant for advanced use cases where more control is needed over
- * the lower-level details of signing. For typical use cases, {@link #Builder(List)} is more
- * appropriate.
- */
- public Builder(ApkSignerEngine signerEngine) {
- if (signerEngine == null) {
- throw new NullPointerException("signerEngine == null");
- }
- mSignerEngine = signerEngine;
- mSignerConfigs = null;
- }
-
- /** Sets the signing configuration of the source stamp to be embedded in the APK. */
- public Builder setSourceStampSignerConfig(SignerConfig sourceStampSignerConfig) {
- mSourceStampSignerConfig = sourceStampSignerConfig;
- return this;
- }
-
- /**
- * Sets the source stamp {@link SigningCertificateLineage}. This structure provides proof of
- * signing certificate rotation for certificates previously used to sign source stamps.
- */
- public Builder setSourceStampSigningCertificateLineage(
- SigningCertificateLineage sourceStampSigningCertificateLineage) {
- mSourceStampSigningCertificateLineage = sourceStampSigningCertificateLineage;
- return this;
- }
-
- /**
- * Sets whether the APK should overwrite existing source stamp, if found.
- *
- * @param force {@code true} to require the APK to be overwrite existing source stamp
- */
- public Builder setForceSourceStampOverwrite(boolean force) {
- mForceSourceStampOverwrite = force;
- return this;
- }
-
- /**
- * Sets the APK to be signed.
- *
- * @see #setInputApk(DataSource)
- */
- public Builder setInputApk(File inputApk) {
- if (inputApk == null) {
- throw new NullPointerException("inputApk == null");
- }
- mInputApkFile = inputApk;
- mInputApkDataSource = null;
- return this;
- }
-
- /**
- * Sets the APK to be signed.
- *
- * @see #setInputApk(File)
- */
- public Builder setInputApk(DataSource inputApk) {
- if (inputApk == null) {
- throw new NullPointerException("inputApk == null");
- }
- mInputApkDataSource = inputApk;
- mInputApkFile = null;
- return this;
- }
-
- /**
- * Sets the location of the output (signed) APK. {@code ApkSigner} will create this file if
- * it doesn't exist.
- *
- * @see #setOutputApk(ReadableDataSink)
- * @see #setOutputApk(DataSink, DataSource)
- */
- public Builder setOutputApk(File outputApk) {
- if (outputApk == null) {
- throw new NullPointerException("outputApk == null");
- }
- mOutputApkFile = outputApk;
- mOutputApkDataSink = null;
- mOutputApkDataSource = null;
- return this;
- }
-
- /**
- * Sets the readable data sink which will receive the output (signed) APK. After signing,
- * the contents of the output APK will be available via the {@link DataSource} interface of
- * the sink.
- *
- * This variant of {@code setOutputApk} is useful for avoiding writing the output APK to
- * a file. For example, an in-memory data sink, such as {@link
- * DataSinks#newInMemoryDataSink()}, could be used instead of a file.
- *
- * @see #setOutputApk(File)
- * @see #setOutputApk(DataSink, DataSource)
- */
- public Builder setOutputApk(ReadableDataSink outputApk) {
- if (outputApk == null) {
- throw new NullPointerException("outputApk == null");
- }
- return setOutputApk(outputApk, outputApk);
- }
-
- /**
- * Sets the sink which will receive the output (signed) APK. Data received by the {@code
- * outputApkOut} sink must be visible through the {@code outputApkIn} data source.
- *
- *
This is an advanced variant of {@link #setOutputApk(ReadableDataSink)}, enabling the
- * sink and the source to be different objects.
- *
- * @see #setOutputApk(ReadableDataSink)
- * @see #setOutputApk(File)
- */
- public Builder setOutputApk(DataSink outputApkOut, DataSource outputApkIn) {
- if (outputApkOut == null) {
- throw new NullPointerException("outputApkOut == null");
- }
- if (outputApkIn == null) {
- throw new NullPointerException("outputApkIn == null");
- }
- mOutputApkFile = null;
- mOutputApkDataSink = outputApkOut;
- mOutputApkDataSource = outputApkIn;
- return this;
- }
-
- /**
- * Sets the location of the V4 output file. {@code ApkSigner} will create this file if it
- * doesn't exist.
- */
- public Builder setV4SignatureOutputFile(File v4SignatureOutputFile) {
- if (v4SignatureOutputFile == null) {
- throw new NullPointerException("v4HashRootOutputFile == null");
- }
- mOutputV4File = v4SignatureOutputFile;
- return this;
- }
-
- /**
- * Sets the minimum Android platform version (API Level) on which APK signatures produced by
- * the signer being built must verify. This method is useful for overriding the default
- * behavior where the minimum API Level is obtained from the {@code android:minSdkVersion}
- * attribute of the APK's {@code AndroidManifest.xml}.
- *
- *
Note: This method may result in APK signatures which don't verify on some
- * Android platform versions supported by the APK.
- *
- *
Note: This method may only be invoked when this builder is not initialized
- * with an {@link ApkSignerEngine}.
- *
- * @throws IllegalStateException if this builder was initialized with an {@link
- * ApkSignerEngine}
- */
- public Builder setMinSdkVersion(int minSdkVersion) {
- checkInitializedWithoutEngine();
- mMinSdkVersion = minSdkVersion;
- return this;
- }
-
- /**
- * Sets whether the APK should be signed using JAR signing (aka v1 signature scheme).
- *
- *
By default, whether APK is signed using JAR signing is determined by {@code
- * ApkSigner}, based on the platform versions supported by the APK or specified using {@link
- * #setMinSdkVersion(int)}. Disabling JAR signing will result in APK signatures which don't
- * verify on Android Marshmallow (Android 6.0, API Level 23) and lower.
- *
- *
Note: This method may only be invoked when this builder is not initialized
- * with an {@link ApkSignerEngine}.
- *
- * @param enabled {@code true} to require the APK to be signed using JAR signing, {@code
- * false} to require the APK to not be signed using JAR signing.
- * @throws IllegalStateException if this builder was initialized with an {@link
- * ApkSignerEngine}
- * @see JAR
- * signing
- */
- public Builder setV1SigningEnabled(boolean enabled) {
- checkInitializedWithoutEngine();
- mV1SigningEnabled = enabled;
- return this;
- }
-
- /**
- * Sets whether the APK should be signed using APK Signature Scheme v2 (aka v2 signature
- * scheme).
- *
- *
By default, whether APK is signed using APK Signature Scheme v2 is determined by
- * {@code ApkSigner} based on the platform versions supported by the APK or specified using
- * {@link #setMinSdkVersion(int)}.
- *
- *
Note: This method may only be invoked when this builder is not initialized
- * with an {@link ApkSignerEngine}.
- *
- * @param enabled {@code true} to require the APK to be signed using APK Signature Scheme
- * v2, {@code false} to require the APK to not be signed using APK Signature Scheme v2.
- * @throws IllegalStateException if this builder was initialized with an {@link
- * ApkSignerEngine}
- * @see APK Signature
- * Scheme v2
- */
- public Builder setV2SigningEnabled(boolean enabled) {
- checkInitializedWithoutEngine();
- mV2SigningEnabled = enabled;
- return this;
- }
-
- /**
- * Sets whether the APK should be signed using APK Signature Scheme v3 (aka v3 signature
- * scheme).
- *
- *
By default, whether APK is signed using APK Signature Scheme v3 is determined by
- * {@code ApkSigner} based on the platform versions supported by the APK or specified using
- * {@link #setMinSdkVersion(int)}.
- *
- *
Note: This method may only be invoked when this builder is not initialized
- * with an {@link ApkSignerEngine}.
- *
- *
Note: APK Signature Scheme v3 only supports a single signing certificate, but
- * may take multiple signers mapping to different targeted platform versions.
- *
- * @param enabled {@code true} to require the APK to be signed using APK Signature Scheme
- * v3, {@code false} to require the APK to not be signed using APK Signature Scheme v3.
- * @throws IllegalStateException if this builder was initialized with an {@link
- * ApkSignerEngine}
- */
- public Builder setV3SigningEnabled(boolean enabled) {
- checkInitializedWithoutEngine();
- mV3SigningEnabled = enabled;
- if (enabled) {
- mV3SigningExplicitlyEnabled = true;
- } else {
- mV3SigningExplicitlyDisabled = true;
- }
- return this;
- }
-
- /**
- * Sets whether the APK should be signed using APK Signature Scheme v4.
- *
- *
V4 signing requires that the APK be v2 or v3 signed.
- *
- * @param enabled {@code true} to require the APK to be signed using APK Signature Scheme v2
- * or v3 and generate an v4 signature file
- */
- public Builder setV4SigningEnabled(boolean enabled) {
- checkInitializedWithoutEngine();
- mV4SigningEnabled = enabled;
- mV4ErrorReportingEnabled = enabled;
- return this;
- }
-
- /**
- * Sets whether errors during v4 signing should be reported and halt the signing process.
- *
- *
Error reporting for v4 signing is disabled by default, but will be enabled if the
- * caller invokes {@link #setV4SigningEnabled} with a value of true. This method is useful
- * for tools that enable v4 signing by default but don't want to fail the signing process if
- * the user did not explicitly request the v4 signing.
- *
- * @param enabled {@code false} to prevent errors encountered during the V4 signing from
- * halting the signing process
- */
- public Builder setV4ErrorReportingEnabled(boolean enabled) {
- checkInitializedWithoutEngine();
- mV4ErrorReportingEnabled = enabled;
- return this;
- }
-
- /**
- * Sets whether to enable the verity signature algorithm for the v2 and v3 signature
- * schemes.
- *
- * @param enabled {@code true} to enable the verity signature algorithm for inclusion in the
- * v2 and v3 signature blocks.
- */
- public Builder setVerityEnabled(boolean enabled) {
- checkInitializedWithoutEngine();
- mVerityEnabled = enabled;
- return this;
- }
-
- /**
- * Sets whether the APK should be signed even if it is marked as debuggable ({@code
- * android:debuggable="true"} in its {@code AndroidManifest.xml}). For backward
- * compatibility reasons, the default value of this setting is {@code true}.
- *
- *
It is dangerous to sign debuggable APKs with production/release keys because Android
- * platform loosens security checks for such APKs. For example, arbitrary unauthorized code
- * may be executed in the context of such an app by anybody with ADB shell access.
- *
- *
Note: This method may only be invoked when this builder is not initialized
- * with an {@link ApkSignerEngine}.
- */
- public Builder setDebuggableApkPermitted(boolean permitted) {
- checkInitializedWithoutEngine();
- mDebuggableApkPermitted = permitted;
- return this;
- }
-
- /**
- * Sets whether signatures produced by signers other than the ones configured in this engine
- * should be copied from the input APK to the output APK.
- *
- *
By default, signatures of other signers are omitted from the output APK.
- *
- *
Note: This method may only be invoked when this builder is not initialized
- * with an {@link ApkSignerEngine}.
- *
- * @throws IllegalStateException if this builder was initialized with an {@link
- * ApkSignerEngine}
- */
- public Builder setOtherSignersSignaturesPreserved(boolean preserved) {
- checkInitializedWithoutEngine();
- mOtherSignersSignaturesPreserved = preserved;
- return this;
- }
-
- /**
- * Sets the value of the {@code Created-By} field in JAR signature files.
- *
- *
Note: This method may only be invoked when this builder is not initialized
- * with an {@link ApkSignerEngine}.
- *
- * @throws IllegalStateException if this builder was initialized with an {@link
- * ApkSignerEngine}
- */
- public Builder setCreatedBy(String createdBy) {
- checkInitializedWithoutEngine();
- if (createdBy == null) {
- throw new NullPointerException();
- }
- mCreatedBy = createdBy;
- return this;
- }
-
- private void checkInitializedWithoutEngine() {
- if (mSignerEngine != null) {
- throw new IllegalStateException(
- "Operation is not available when builder initialized with an engine");
- }
- }
-
- /**
- * Sets the {@link SigningCertificateLineage} to use with the v3 signature scheme. This
- * structure provides proof of signing certificate rotation linking {@link SignerConfig}
- * objects to previous ones.
- */
- public Builder setSigningCertificateLineage(
- SigningCertificateLineage signingCertificateLineage) {
- if (signingCertificateLineage != null) {
- mV3SigningEnabled = true;
- mSigningCertificateLineage = signingCertificateLineage;
- }
- return this;
- }
-
- /**
- * Returns a new {@code ApkSigner} instance initialized according to the configuration of
- * this builder.
- */
- public ApkSigner build() {
- if (mV3SigningExplicitlyDisabled && mV3SigningExplicitlyEnabled) {
- throw new IllegalStateException(
- "Builder configured to both enable and disable APK "
- + "Signature Scheme v3 signing");
- }
-
- if (mV3SigningExplicitlyDisabled) {
- mV3SigningEnabled = false;
- }
-
- if (mV3SigningExplicitlyEnabled) {
- mV3SigningEnabled = true;
- }
-
- // If V4 signing is not explicitly set, and V2/V3 signing is disabled, then V4 signing
- // must be disabled as well as it is dependent on V2/V3.
- if (mV4SigningEnabled && !mV2SigningEnabled && !mV3SigningEnabled) {
- if (!mV4ErrorReportingEnabled) {
- mV4SigningEnabled = false;
- } else {
- throw new IllegalStateException(
- "APK Signature Scheme v4 signing requires at least "
- + "v2 or v3 signing to be enabled");
- }
- }
-
- // TODO - if v3 signing is enabled, check provided signers and history to see if valid
-
- return new ApkSigner(
- mSignerConfigs,
- mSourceStampSignerConfig,
- mSourceStampSigningCertificateLineage,
- mForceSourceStampOverwrite,
- mMinSdkVersion,
- mV1SigningEnabled,
- mV2SigningEnabled,
- mV3SigningEnabled,
- mV4SigningEnabled,
- mVerityEnabled,
- mV4ErrorReportingEnabled,
- mDebuggableApkPermitted,
- mOtherSignersSignaturesPreserved,
- mCreatedBy,
- mSignerEngine,
- mInputApkFile,
- mInputApkDataSource,
- mOutputApkFile,
- mOutputApkDataSink,
- mOutputApkDataSource,
- mOutputV4File,
- mSigningCertificateLineage);
- }
- }
-}
diff --git a/app/src/main/java/com/android/apksig/ApkSignerEngine.java b/app/src/main/java/com/android/apksig/ApkSignerEngine.java
deleted file mode 100644
index c79f232707..0000000000
--- a/app/src/main/java/com/android/apksig/ApkSignerEngine.java
+++ /dev/null
@@ -1,550 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.apksig;
-
-import com.android.apksig.apk.ApkFormatException;
-import com.android.apksig.util.DataSink;
-import com.android.apksig.util.DataSource;
-import com.android.apksig.util.RunnablesExecutor;
-
-import java.io.Closeable;
-import java.io.File;
-import java.io.IOException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.SignatureException;
-import java.util.List;
-import java.util.Set;
-
-/**
- * APK signing logic which is independent of how input and output APKs are stored, parsed, and
- * generated.
- *
- *
Operating Model
- *
- * The abstract operating model is that there is an input APK which is being signed, thus producing
- * an output APK. In reality, there may be just an output APK being built from scratch, or the input
- * APK and the output APK may be the same file. Because this engine does not deal with reading and
- * writing files, it can handle all of these scenarios.
- *
- * The engine is stateful and thus cannot be used for signing multiple APKs. However, once
- * the engine signed an APK, the engine can be used to re-sign the APK after it has been modified.
- * This may be more efficient than signing the APK using a new instance of the engine. See
- * Incremental Operation.
- *
- *
In the engine's operating model, a signed APK is produced as follows.
- *
- * - JAR entries to be signed are output,
- * - JAR archive is signed using JAR signing, thus adding the so-called v1 signature to the
- * output,
- * - JAR archive is signed using APK Signature Scheme v2, thus adding the so-called v2 signature
- * to the output.
- *
- *
- * The input APK may contain JAR entries which, depending on the engine's configuration, may or
- * may not be output (e.g., existing signatures may need to be preserved or stripped) or which the
- * engine will overwrite as part of signing. The engine thus offers {@link #inputJarEntry(String)}
- * which tells the client whether the input JAR entry needs to be output. This avoids the need for
- * the client to hard-code the aspects of APK signing which determine which parts of input must be
- * ignored. Similarly, the engine offers {@link #inputApkSigningBlock(DataSource)} to help the
- * client avoid dealing with preserving or stripping APK Signature Scheme v2 signature of the input
- * APK.
- *
- *
To use the engine to sign an input APK (or a collection of JAR entries), follow these
- * steps:
- *
- * - Obtain a new instance of the engine -- engine instances are stateful and thus cannot be used
- * for signing multiple APKs.
- * - Locate the input APK's APK Signing Block and provide it to
- * {@link #inputApkSigningBlock(DataSource)}.
- * - For each JAR entry in the input APK, invoke {@link #inputJarEntry(String)} to determine
- * whether this entry should be output. The engine may request to inspect the entry.
- * - For each output JAR entry, invoke {@link #outputJarEntry(String)} which may request to
- * inspect the entry.
- * - Once all JAR entries have been output, invoke {@link #outputJarEntries()} which may request
- * that additional JAR entries are output. These entries comprise the output APK's JAR
- * signature.
- * - Locate the ZIP Central Directory and ZIP End of Central Directory sections in the output and
- * invoke {@link #outputZipSections2(DataSource, DataSource, DataSource)} which may request that
- * an APK Signature Block is inserted before the ZIP Central Directory. The block contains the
- * output APK's APK Signature Scheme v2 signature.
- * - Invoke {@link #outputDone()} to signal that the APK was output in full. The engine will
- * confirm that the output APK is signed.
- * - Invoke {@link #close()} to signal that the engine will no longer be used. This lets the
- * engine free any resources it no longer needs.
- *
- *
- * Some invocations of the engine may provide the client with a task to perform. The client is
- * expected to perform all requested tasks before proceeding to the next stage of signing. See
- * documentation of each method about the deadlines for performing the tasks requested by the
- * method.
- *
- *
Incremental Operation
- *
- * The engine supports incremental operation where a signed APK is produced, then modified and
- * re-signed. This may be useful for IDEs, where an app is frequently re-signed after small changes
- * by the developer. Re-signing may be more efficient than signing from scratch.
- *
- * To use the engine in incremental mode, keep notifying the engine of changes to the APK through
- * {@link #inputApkSigningBlock(DataSource)}, {@link #inputJarEntry(String)},
- * {@link #inputJarEntryRemoved(String)}, {@link #outputJarEntry(String)},
- * and {@link #outputJarEntryRemoved(String)}, perform the tasks requested by the engine through
- * these methods, and, when a new signed APK is desired, run through steps 5 onwards to re-sign the
- * APK.
- *
- *
Output-only Operation
- *
- * The engine's abstract operating model consists of an input APK and an output APK. However, it is
- * possible to use the engine in output-only mode where the engine's {@code input...} methods are
- * not invoked. In this mode, the engine has less control over output because it cannot request that
- * some JAR entries are not output. Nevertheless, the engine will attempt to make the output APK
- * signed and will report an error if cannot do so.
- *
- * @see Application Signing
- */
-public interface ApkSignerEngine extends Closeable {
-
- default void setExecutor(RunnablesExecutor executor) {
- throw new UnsupportedOperationException("setExecutor method is not implemented");
- }
-
- /**
- * Initializes the signer engine with the data already present in the apk (if any). There
- * might already be data that can be reused if the entries has not been changed.
- *
- * @param manifestBytes
- * @param entryNames
- * @return set of entry names which were processed by the engine during the initialization, a
- * subset of entryNames
- */
- default Set initWith(byte[] manifestBytes, Set entryNames) {
- throw new UnsupportedOperationException("initWith method is not implemented");
- }
-
- /**
- * Indicates to this engine that the input APK contains the provided APK Signing Block. The
- * block may contain signatures of the input APK, such as APK Signature Scheme v2 signatures.
- *
- * @param apkSigningBlock APK signing block of the input APK. The provided data source is
- * guaranteed to not be used by the engine after this method terminates.
- *
- * @throws IOException if an I/O error occurs while reading the APK Signing Block
- * @throws ApkFormatException if the APK Signing Block is malformed
- * @throws IllegalStateException if this engine is closed
- */
- void inputApkSigningBlock(DataSource apkSigningBlock)
- throws IOException, ApkFormatException, IllegalStateException;
-
- /**
- * Indicates to this engine that the specified JAR entry was encountered in the input APK.
- *
- * When an input entry is updated/changed, it's OK to not invoke
- * {@link #inputJarEntryRemoved(String)} before invoking this method.
- *
- * @return instructions about how to proceed with this entry
- *
- * @throws IllegalStateException if this engine is closed
- */
- InputJarEntryInstructions inputJarEntry(String entryName) throws IllegalStateException;
-
- /**
- * Indicates to this engine that the specified JAR entry was output.
- *
- *
It is unnecessary to invoke this method for entries added to output by this engine (e.g.,
- * requested by {@link #outputJarEntries()}) provided the entries were output with exactly the
- * data requested by the engine.
- *
- *
When an already output entry is updated/changed, it's OK to not invoke
- * {@link #outputJarEntryRemoved(String)} before invoking this method.
- *
- * @return request to inspect the entry or {@code null} if the engine does not need to inspect
- * the entry. The request must be fulfilled before {@link #outputJarEntries()} is
- * invoked.
- *
- * @throws IllegalStateException if this engine is closed
- */
- InspectJarEntryRequest outputJarEntry(String entryName) throws IllegalStateException;
-
- /**
- * Indicates to this engine that the specified JAR entry was removed from the input. It's safe
- * to invoke this for entries for which {@link #inputJarEntry(String)} hasn't been invoked.
- *
- * @return output policy of this JAR entry. The policy indicates how this input entry affects
- * the output APK. The client of this engine should use this information to determine
- * how the removal of this input APK's JAR entry affects the output APK.
- *
- * @throws IllegalStateException if this engine is closed
- */
- InputJarEntryInstructions.OutputPolicy inputJarEntryRemoved(String entryName)
- throws IllegalStateException;
-
- /**
- * Indicates to this engine that the specified JAR entry was removed from the output. It's safe
- * to invoke this for entries for which {@link #outputJarEntry(String)} hasn't been invoked.
- *
- * @throws IllegalStateException if this engine is closed
- */
- void outputJarEntryRemoved(String entryName) throws IllegalStateException;
-
- /**
- * Indicates to this engine that all JAR entries have been output.
- *
- * @return request to add JAR signature to the output or {@code null} if there is no need to add
- * a JAR signature. The request will contain additional JAR entries to be output. The
- * request must be fulfilled before
- * {@link #outputZipSections2(DataSource, DataSource, DataSource)} is invoked.
- *
- * @throws ApkFormatException if the APK is malformed in a way which is preventing this engine
- * from producing a valid signature. For example, if the engine uses the provided
- * {@code META-INF/MANIFEST.MF} as a template and the file is malformed.
- * @throws NoSuchAlgorithmException if a signature could not be generated because a required
- * cryptographic algorithm implementation is missing
- * @throws InvalidKeyException if a signature could not be generated because a signing key is
- * not suitable for generating the signature
- * @throws SignatureException if an error occurred while generating a signature
- * @throws IllegalStateException if there are unfulfilled requests, such as to inspect some JAR
- * entries, or if the engine is closed
- */
- OutputJarSignatureRequest outputJarEntries()
- throws ApkFormatException, NoSuchAlgorithmException, InvalidKeyException,
- SignatureException, IllegalStateException;
-
- /**
- * Indicates to this engine that the ZIP sections comprising the output APK have been output.
- *
- *
The provided data sources are guaranteed to not be used by the engine after this method
- * terminates.
- *
- * @deprecated This is now superseded by {@link #outputZipSections2(DataSource, DataSource,
- * DataSource)}.
- *
- * @param zipEntries the section of ZIP archive containing Local File Header records and data of
- * the ZIP entries. In a well-formed archive, this section starts at the start of the
- * archive and extends all the way to the ZIP Central Directory.
- * @param zipCentralDirectory ZIP Central Directory section
- * @param zipEocd ZIP End of Central Directory (EoCD) record
- *
- * @return request to add an APK Signing Block to the output or {@code null} if the output must
- * not contain an APK Signing Block. The request must be fulfilled before
- * {@link #outputDone()} is invoked.
- *
- * @throws IOException if an I/O error occurs while reading the provided ZIP sections
- * @throws ApkFormatException if the provided APK is malformed in a way which prevents this
- * engine from producing a valid signature. For example, if the APK Signing Block
- * provided to the engine is malformed.
- * @throws NoSuchAlgorithmException if a signature could not be generated because a required
- * cryptographic algorithm implementation is missing
- * @throws InvalidKeyException if a signature could not be generated because a signing key is
- * not suitable for generating the signature
- * @throws SignatureException if an error occurred while generating a signature
- * @throws IllegalStateException if there are unfulfilled requests, such as to inspect some JAR
- * entries or to output JAR signature, or if the engine is closed
- */
- @Deprecated
- OutputApkSigningBlockRequest outputZipSections(
- DataSource zipEntries,
- DataSource zipCentralDirectory,
- DataSource zipEocd)
- throws IOException, ApkFormatException, NoSuchAlgorithmException,
- InvalidKeyException, SignatureException, IllegalStateException;
-
- /**
- * Indicates to this engine that the ZIP sections comprising the output APK have been output.
- *
- *
The provided data sources are guaranteed to not be used by the engine after this method
- * terminates.
- *
- * @param zipEntries the section of ZIP archive containing Local File Header records and data of
- * the ZIP entries. In a well-formed archive, this section starts at the start of the
- * archive and extends all the way to the ZIP Central Directory.
- * @param zipCentralDirectory ZIP Central Directory section
- * @param zipEocd ZIP End of Central Directory (EoCD) record
- *
- * @return request to add an APK Signing Block to the output or {@code null} if the output must
- * not contain an APK Signing Block. The request must be fulfilled before
- * {@link #outputDone()} is invoked.
- *
- * @throws IOException if an I/O error occurs while reading the provided ZIP sections
- * @throws ApkFormatException if the provided APK is malformed in a way which prevents this
- * engine from producing a valid signature. For example, if the APK Signing Block
- * provided to the engine is malformed.
- * @throws NoSuchAlgorithmException if a signature could not be generated because a required
- * cryptographic algorithm implementation is missing
- * @throws InvalidKeyException if a signature could not be generated because a signing key is
- * not suitable for generating the signature
- * @throws SignatureException if an error occurred while generating a signature
- * @throws IllegalStateException if there are unfulfilled requests, such as to inspect some JAR
- * entries or to output JAR signature, or if the engine is closed
- */
- OutputApkSigningBlockRequest2 outputZipSections2(
- DataSource zipEntries,
- DataSource zipCentralDirectory,
- DataSource zipEocd)
- throws IOException, ApkFormatException, NoSuchAlgorithmException,
- InvalidKeyException, SignatureException, IllegalStateException;
-
- /**
- * Indicates to this engine that the signed APK was output.
- *
- *
This does not change the output APK. The method helps the client confirm that the current
- * output is signed.
- *
- * @throws IllegalStateException if there are unfulfilled requests, such as to inspect some JAR
- * entries or to output signatures, or if the engine is closed
- */
- void outputDone() throws IllegalStateException;
-
- /**
- * Generates a V4 signature proto and write to output file.
- *
- * @param data Input data to calculate a verity hash tree and hash root
- * @param outputFile To store the serialized V4 Signature.
- * @param ignoreFailures Whether any failures will be silently ignored.
- * @throws InvalidKeyException if a signature could not be generated because a signing key is
- * not suitable for generating the signature
- * @throws NoSuchAlgorithmException if a signature could not be generated because a required
- * cryptographic algorithm implementation is missing
- * @throws SignatureException if an error occurred while generating a signature
- * @throws IOException if protobuf fails to be serialized and written to file
- */
- void signV4(DataSource data, File outputFile, boolean ignoreFailures)
- throws InvalidKeyException, NoSuchAlgorithmException, SignatureException, IOException;
-
- /**
- * Checks if the signing configuration provided to the engine is capable of creating a
- * SourceStamp.
- */
- default boolean isEligibleForSourceStamp() {
- return false;
- }
-
- /** Generates the digest of the certificate used to sign the source stamp. */
- default byte[] generateSourceStampCertificateDigest() throws SignatureException {
- return new byte[0];
- }
-
- /**
- * Indicates to this engine that it will no longer be used. Invoking this on an already closed
- * engine is OK.
- *
- *
This does not change the output APK. For example, if the output APK is not yet fully
- * signed, it will remain so after this method terminates.
- */
- @Override
- void close();
-
- /**
- * Instructions about how to handle an input APK's JAR entry.
- *
- *
The instructions indicate whether to output the entry (see {@link #getOutputPolicy()}) and
- * may contain a request to inspect the entry (see {@link #getInspectJarEntryRequest()}), in
- * which case the request must be fulfilled before {@link ApkSignerEngine#outputJarEntries()} is
- * invoked.
- */
- public static class InputJarEntryInstructions {
- private final OutputPolicy mOutputPolicy;
- private final InspectJarEntryRequest mInspectJarEntryRequest;
-
- /**
- * Constructs a new {@code InputJarEntryInstructions} instance with the provided entry
- * output policy and without a request to inspect the entry.
- */
- public InputJarEntryInstructions(OutputPolicy outputPolicy) {
- this(outputPolicy, null);
- }
-
- /**
- * Constructs a new {@code InputJarEntryInstructions} instance with the provided entry
- * output mode and with the provided request to inspect the entry.
- *
- * @param inspectJarEntryRequest request to inspect the entry or {@code null} if there's no
- * need to inspect the entry.
- */
- public InputJarEntryInstructions(
- OutputPolicy outputPolicy,
- InspectJarEntryRequest inspectJarEntryRequest) {
- mOutputPolicy = outputPolicy;
- mInspectJarEntryRequest = inspectJarEntryRequest;
- }
-
- /**
- * Returns the output policy for this entry.
- */
- public OutputPolicy getOutputPolicy() {
- return mOutputPolicy;
- }
-
- /**
- * Returns the request to inspect the JAR entry or {@code null} if there is no need to
- * inspect the entry.
- */
- public InspectJarEntryRequest getInspectJarEntryRequest() {
- return mInspectJarEntryRequest;
- }
-
- /**
- * Output policy for an input APK's JAR entry.
- */
- public static enum OutputPolicy {
- /** Entry must not be output. */
- SKIP,
-
- /** Entry should be output. */
- OUTPUT,
-
- /** Entry will be output by the engine. The client can thus ignore this input entry. */
- OUTPUT_BY_ENGINE,
- }
- }
-
- /**
- * Request to inspect the specified JAR entry.
- *
- *
The entry's uncompressed data must be provided to the data sink returned by
- * {@link #getDataSink()}. Once the entry's data has been provided to the sink, {@link #done()}
- * must be invoked.
- */
- interface InspectJarEntryRequest {
-
- /**
- * Returns the data sink into which the entry's uncompressed data should be sent.
- */
- DataSink getDataSink();
-
- /**
- * Indicates that entry's data has been provided in full.
- */
- void done();
-
- /**
- * Returns the name of the JAR entry.
- */
- String getEntryName();
- }
-
- /**
- * Request to add JAR signature (aka v1 signature) to the output APK.
- *
- *
Entries listed in {@link #getAdditionalJarEntries()} must be added to the output APK after
- * which {@link #done()} must be invoked.
- */
- interface OutputJarSignatureRequest {
-
- /**
- * Returns JAR entries that must be added to the output APK.
- */
- List getAdditionalJarEntries();
-
- /**
- * Indicates that the JAR entries contained in this request were added to the output APK.
- */
- void done();
-
- /**
- * JAR entry.
- */
- public static class JarEntry {
- private final String mName;
- private final byte[] mData;
-
- /**
- * Constructs a new {@code JarEntry} with the provided name and data.
- *
- * @param data uncompressed data of the entry. Changes to this array will not be
- * reflected in {@link #getData()}.
- */
- public JarEntry(String name, byte[] data) {
- mName = name;
- mData = data.clone();
- }
-
- /**
- * Returns the name of this ZIP entry.
- */
- public String getName() {
- return mName;
- }
-
- /**
- * Returns the uncompressed data of this JAR entry.
- */
- public byte[] getData() {
- return mData.clone();
- }
- }
- }
-
- /**
- * Request to add the specified APK Signing Block to the output APK. APK Signature Scheme v2
- * signature(s) of the APK are contained in this block.
- *
- * The APK Signing Block returned by {@link #getApkSigningBlock()} must be placed into the
- * output APK such that the block is immediately before the ZIP Central Directory, the offset of
- * ZIP Central Directory in the ZIP End of Central Directory record must be adjusted
- * accordingly, and then {@link #done()} must be invoked.
- *
- *
If the output contains an APK Signing Block, that block must be replaced by the block
- * contained in this request.
- *
- * @deprecated This is now superseded by {@link OutputApkSigningBlockRequest2}.
- */
- @Deprecated
- interface OutputApkSigningBlockRequest {
-
- /**
- * Returns the APK Signing Block.
- */
- byte[] getApkSigningBlock();
-
- /**
- * Indicates that the APK Signing Block was output as requested.
- */
- void done();
- }
-
- /**
- * Request to add the specified APK Signing Block to the output APK. APK Signature Scheme v2
- * signature(s) of the APK are contained in this block.
- *
- *
The APK Signing Block returned by {@link #getApkSigningBlock()} must be placed into the
- * output APK such that the block is immediately before the ZIP Central Directory. Immediately
- * before the APK Signing Block must be padding consists of the number of 0x00 bytes returned by
- * {@link #getPaddingSizeBeforeApkSigningBlock()}. The offset of ZIP Central Directory in the
- * ZIP End of Central Directory record must be adjusted accordingly, and then {@link #done()}
- * must be invoked.
- *
- *
If the output contains an APK Signing Block, that block must be replaced by the block
- * contained in this request.
- */
- interface OutputApkSigningBlockRequest2 {
- /**
- * Returns the APK Signing Block.
- */
- byte[] getApkSigningBlock();
-
- /**
- * Indicates that the APK Signing Block was output as requested.
- */
- void done();
-
- /**
- * Returns the number of 0x00 bytes the caller must place immediately before APK Signing
- * Block.
- */
- int getPaddingSizeBeforeApkSigningBlock();
- }
-}
diff --git a/app/src/main/java/com/android/apksig/ApkVerificationIssue.java b/app/src/main/java/com/android/apksig/ApkVerificationIssue.java
deleted file mode 100644
index 2aa9d0be84..0000000000
--- a/app/src/main/java/com/android/apksig/ApkVerificationIssue.java
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.apksig;
-
-/**
- * This class is intended as a lightweight representation of an APK signature verification issue
- * where the client does not require the additional textual details provided by a subclass.
- */
-public class ApkVerificationIssue {
- /* The V2 signer(s) could not be read from the V2 signature block */
- public static final int V2_SIG_MALFORMED_SIGNERS = 1;
- /* A V2 signature block exists without any V2 signers */
- public static final int V2_SIG_NO_SIGNERS = 2;
- /* Failed to parse a signer's block in the V2 signature block */
- public static final int V2_SIG_MALFORMED_SIGNER = 3;
- /* Failed to parse the signer's signature record in the V2 signature block */
- public static final int V2_SIG_MALFORMED_SIGNATURE = 4;
- /* The V2 signer contained no signatures */
- public static final int V2_SIG_NO_SIGNATURES = 5;
- /* The V2 signer's certificate could not be parsed */
- public static final int V2_SIG_MALFORMED_CERTIFICATE = 6;
- /* No signing certificates exist for the V2 signer */
- public static final int V2_SIG_NO_CERTIFICATES = 7;
- /* Failed to parse the V2 signer's digest record */
- public static final int V2_SIG_MALFORMED_DIGEST = 8;
- /* The V3 signer(s) could not be read from the V3 signature block */
- public static final int V3_SIG_MALFORMED_SIGNERS = 9;
- /* A V3 signature block exists without any V3 signers */
- public static final int V3_SIG_NO_SIGNERS = 10;
- /* Failed to parse a signer's block in the V3 signature block */
- public static final int V3_SIG_MALFORMED_SIGNER = 11;
- /* Failed to parse the signer's signature record in the V3 signature block */
- public static final int V3_SIG_MALFORMED_SIGNATURE = 12;
- /* The V3 signer contained no signatures */
- public static final int V3_SIG_NO_SIGNATURES = 13;
- /* The V3 signer's certificate could not be parsed */
- public static final int V3_SIG_MALFORMED_CERTIFICATE = 14;
- /* No signing certificates exist for the V3 signer */
- public static final int V3_SIG_NO_CERTIFICATES = 15;
- /* Failed to parse the V3 signer's digest record */
- public static final int V3_SIG_MALFORMED_DIGEST = 16;
- /* The source stamp signer contained no signatures */
- public static final int SOURCE_STAMP_NO_SIGNATURE = 17;
- /* The source stamp signer's certificate could not be parsed */
- public static final int SOURCE_STAMP_MALFORMED_CERTIFICATE = 18;
- /* The source stamp contains a signature produced using an unknown algorithm */
- public static final int SOURCE_STAMP_UNKNOWN_SIG_ALGORITHM = 19;
- /* Failed to parse the signer's signature in the source stamp signature block */
- public static final int SOURCE_STAMP_MALFORMED_SIGNATURE = 20;
- /* The source stamp's signature block failed verification */
- public static final int SOURCE_STAMP_DID_NOT_VERIFY = 21;
- /* An exception was encountered when verifying the source stamp */
- public static final int SOURCE_STAMP_VERIFY_EXCEPTION = 22;
- /* The certificate digest in the APK does not match the expected digest */
- public static final int SOURCE_STAMP_EXPECTED_DIGEST_MISMATCH = 23;
- /*
- * The APK contains a source stamp signature block without a corresponding stamp certificate
- * digest in the APK contents.
- */
- public static final int SOURCE_STAMP_SIGNATURE_BLOCK_WITHOUT_CERT_DIGEST = 24;
- /*
- * The APK does not contain the source stamp certificate digest file nor the source stamp
- * signature block.
- */
- public static final int SOURCE_STAMP_CERT_DIGEST_AND_SIG_BLOCK_MISSING = 25;
- /*
- * None of the signatures provided by the source stamp were produced with a known signature
- * algorithm.
- */
- public static final int SOURCE_STAMP_NO_SUPPORTED_SIGNATURE = 26;
- /*
- * The source stamp signer's certificate in the signing block does not match the certificate in
- * the APK.
- */
- public static final int SOURCE_STAMP_CERTIFICATE_MISMATCH_BETWEEN_SIGNATURE_BLOCK_AND_APK = 27;
- /* The APK could not be properly parsed due to a ZIP or APK format exception */
- public static final int MALFORMED_APK = 28;
- /* An unexpected exception was caught when attempting to verify the APK's signatures */
- public static final int UNEXPECTED_EXCEPTION = 29;
- /* The APK contains the certificate digest file but does not contain a stamp signature block */
- public static final int SOURCE_STAMP_SIG_MISSING = 30;
- /* Source stamp block contains a malformed attribute. */
- public static final int SOURCE_STAMP_MALFORMED_ATTRIBUTE = 31;
- /* Source stamp block contains an unknown attribute. */
- public static final int SOURCE_STAMP_UNKNOWN_ATTRIBUTE = 32;
- /**
- * Failed to parse the SigningCertificateLineage structure in the source stamp
- * attributes section.
- */
- public static final int SOURCE_STAMP_MALFORMED_LINEAGE = 33;
- /**
- * The source stamp certificate does not match the terminal node in the provided
- * proof-of-rotation structure describing the stamp certificate history.
- */
- public static final int SOURCE_STAMP_POR_CERT_MISMATCH = 34;
- /**
- * The source stamp SigningCertificateLineage attribute contains a proof-of-rotation record
- * with signature(s) that did not verify.
- */
- public static final int SOURCE_STAMP_POR_DID_NOT_VERIFY = 35;
- /** No V1 / jar signing signature blocks were found in the APK. */
- public static final int JAR_SIG_NO_SIGNATURES = 36;
- /** An exception was encountered when parsing the V1 / jar signer in the signature block. */
- public static final int JAR_SIG_PARSE_EXCEPTION = 37;
-
- private final int mIssueId;
- private final String mFormat;
- private final Object[] mParams;
-
- /**
- * Constructs a new {@code ApkVerificationIssue} using the provided {@code format} string and
- * {@code params}.
- */
- public ApkVerificationIssue(String format, Object... params) {
- mIssueId = -1;
- mFormat = format;
- mParams = params;
- }
-
- /**
- * Constructs a new {@code ApkVerificationIssue} using the provided {@code issueId} and {@code
- * params}.
- */
- public ApkVerificationIssue(int issueId, Object... params) {
- mIssueId = issueId;
- mFormat = null;
- mParams = params;
- }
-
- /**
- * Returns the numeric ID for this issue.
- */
- public int getIssueId() {
- return mIssueId;
- }
-
- /**
- * Returns the optional parameters for this issue.
- */
- public Object[] getParams() {
- return mParams;
- }
-
- @Override
- public String toString() {
- // If this instance was created by a subclass with a format string then return the same
- // formatted String as the subclass.
- if (mFormat != null) {
- return String.format(mFormat, mParams);
- }
- StringBuilder result = new StringBuilder("mIssueId: ").append(mIssueId);
- for (Object param : mParams) {
- result.append(", ").append(param.toString());
- }
- return result.toString();
- }
-}
diff --git a/app/src/main/java/com/android/apksig/ApkVerifier.java b/app/src/main/java/com/android/apksig/ApkVerifier.java
deleted file mode 100644
index e6eae6b172..0000000000
--- a/app/src/main/java/com/android/apksig/ApkVerifier.java
+++ /dev/null
@@ -1,3329 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.apksig;
-
-import static com.android.apksig.apk.ApkUtils.SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME;
-import static com.android.apksig.apk.ApkUtils.computeSha256DigestBytes;
-import static com.android.apksig.apk.ApkUtils.getTargetSandboxVersionFromBinaryAndroidManifest;
-import static com.android.apksig.apk.ApkUtils.getTargetSdkVersionFromBinaryAndroidManifest;
-import static com.android.apksig.internal.apk.ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2;
-import static com.android.apksig.internal.apk.ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3;
-import static com.android.apksig.internal.apk.ApkSigningBlockUtils.VERSION_JAR_SIGNATURE_SCHEME;
-import static com.android.apksig.internal.apk.v1.V1SchemeConstants.MANIFEST_ENTRY_NAME;
-
-import com.android.apksig.apk.ApkFormatException;
-import com.android.apksig.apk.ApkUtils;
-import com.android.apksig.internal.apk.ApkSigResult;
-import com.android.apksig.internal.apk.ApkSignerInfo;
-import com.android.apksig.internal.apk.ApkSigningBlockUtils;
-import com.android.apksig.internal.apk.ContentDigestAlgorithm;
-import com.android.apksig.internal.apk.SignatureAlgorithm;
-import com.android.apksig.internal.apk.SignatureInfo;
-import com.android.apksig.internal.apk.SignatureNotFoundException;
-import com.android.apksig.internal.apk.stamp.SourceStampConstants;
-import com.android.apksig.internal.apk.stamp.V2SourceStampVerifier;
-import com.android.apksig.internal.apk.v1.V1SchemeVerifier;
-import com.android.apksig.internal.apk.v2.V2SchemeConstants;
-import com.android.apksig.internal.apk.v2.V2SchemeVerifier;
-import com.android.apksig.internal.apk.v3.V3SchemeConstants;
-import com.android.apksig.internal.apk.v3.V3SchemeVerifier;
-import com.android.apksig.internal.apk.v4.V4SchemeVerifier;
-import com.android.apksig.internal.util.AndroidSdkVersion;
-import com.android.apksig.internal.zip.CentralDirectoryRecord;
-import com.android.apksig.internal.zip.LocalFileRecord;
-import com.android.apksig.util.DataSource;
-import com.android.apksig.util.DataSources;
-import com.android.apksig.util.RunnablesExecutor;
-import com.android.apksig.zip.ZipFormatException;
-
-import java.io.Closeable;
-import java.io.File;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.nio.ByteBuffer;
-import java.security.NoSuchAlgorithmException;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.EnumMap;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * APK signature verifier which mimics the behavior of the Android platform.
- *
- *
The verifier is designed to closely mimic the behavior of Android platforms. This is to enable
- * the verifier to be used for checking whether an APK's signatures are expected to verify on
- * Android.
- *
- *
Use {@link Builder} to obtain instances of this verifier.
- *
- * @see Application Signing
- */
-public class ApkVerifier {
-
- private static final Map SUPPORTED_APK_SIG_SCHEME_NAMES =
- loadSupportedApkSigSchemeNames();
-
- private static Map loadSupportedApkSigSchemeNames() {
- Map supportedMap = new HashMap<>(2);
- supportedMap.put(
- ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2, "APK Signature Scheme v2");
- supportedMap.put(
- ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3, "APK Signature Scheme v3");
- return supportedMap;
- }
-
- private final File mApkFile;
- private final DataSource mApkDataSource;
- private final File mV4SignatureFile;
-
- private final Integer mMinSdkVersion;
- private final int mMaxSdkVersion;
-
- private ApkVerifier(
- File apkFile,
- DataSource apkDataSource,
- File v4SignatureFile,
- Integer minSdkVersion,
- int maxSdkVersion) {
- mApkFile = apkFile;
- mApkDataSource = apkDataSource;
- mV4SignatureFile = v4SignatureFile;
- mMinSdkVersion = minSdkVersion;
- mMaxSdkVersion = maxSdkVersion;
- }
-
- /**
- * Verifies the APK's signatures and returns the result of verification. The APK can be
- * considered verified iff the result's {@link Result#isVerified()} returns {@code true}.
- * The verification result also includes errors, warnings, and information about signers such
- * as their signing certificates.
- *
- * Verification succeeds iff the APK's signature is expected to verify on all Android
- * platform versions specified via the {@link Builder}. If the APK's signature is expected to
- * not verify on any of the specified platform versions, this method returns a result with one
- * or more errors and whose {@link Result#isVerified()} returns {@code false}, or this method
- * throws an exception.
- *
- * @throws IOException if an I/O error is encountered while reading the APK
- * @throws ApkFormatException if the APK is malformed
- * @throws NoSuchAlgorithmException if the APK's signatures cannot be verified because a
- * required cryptographic algorithm implementation is missing
- * @throws IllegalStateException if this verifier's configuration is missing required
- * information.
- */
- public Result verify() throws IOException, ApkFormatException, NoSuchAlgorithmException,
- IllegalStateException {
- Closeable in = null;
- try {
- DataSource apk;
- if (mApkDataSource != null) {
- apk = mApkDataSource;
- } else if (mApkFile != null) {
- RandomAccessFile f = new RandomAccessFile(mApkFile, "r");
- in = f;
- apk = DataSources.asDataSource(f, 0, f.length());
- } else {
- throw new IllegalStateException("APK not provided");
- }
- return verify(apk);
- } finally {
- if (in != null) {
- in.close();
- }
- }
- }
-
- /**
- * Simply retrieve result that contains V1Signature only by ignoring all the verification process.
- *
- * @throws IOException if an I/O error is encountered while reading the APK
- * @throws ApkFormatException if the APK is malformed
- * @throws NoSuchAlgorithmException if the APK's signatures cannot be verified because a
- * required cryptographic algorithm implementation is missing
- * @throws IllegalStateException if this verifier's configuration is missing required
- * information.
- */
- public Result retrieveV1Signature() throws IOException, ApkFormatException, NoSuchAlgorithmException {
- Closeable in = null;
- try {
- DataSource apk;
- if (mApkDataSource != null) {
- apk = mApkDataSource;
- } else if (mApkFile != null) {
- RandomAccessFile f = new RandomAccessFile(mApkFile, "r");
- in = f;
- apk = DataSources.asDataSource(f, 0, f.length());
- } else {
- throw new IllegalStateException("APK not provided");
- }
- return retrieveV1Signature(apk);
- } finally {
- if (in != null) {
- in.close();
- }
- }
- }
-
- /**
- * Simply retrieve result that contains V2Signature only by ignoring all the verification process.
- *
- * @throws IOException if an I/O error is encountered while reading the APK
- * @throws ApkFormatException if the APK is malformed
- * @throws NoSuchAlgorithmException if the APK's signatures cannot be verified because a
- * required cryptographic algorithm implementation is missing
- * @throws IllegalStateException if this verifier's configuration is missing required
- * information.
- */
- public Result retrieveV2Signature() throws IOException, ApkFormatException, NoSuchAlgorithmException {
- Closeable in = null;
- try {
- DataSource apk;
- if (mApkDataSource != null) {
- apk = mApkDataSource;
- } else if (mApkFile != null) {
- RandomAccessFile f = new RandomAccessFile(mApkFile, "r");
- in = f;
- apk = DataSources.asDataSource(f, 0, f.length());
- } else {
- throw new IllegalStateException("APK not provided");
- }
- return retrieveV2Signature(apk);
- } finally {
- if (in != null) {
- in.close();
- }
- }
- }
-
- /**
- * Simply retrieve result that contains V2Signature only by ignoring all the verification process.
- *
- * @param apk APK file contents
- * @throws IOException if an I/O error is encountered while reading the APK
- * @throws ApkFormatException if the APK is malformed
- * @throws NoSuchAlgorithmException if the APK's signatures cannot be verified because a
- * required cryptographic algorithm implementation is missing
- */
- private Result retrieveV2Signature(DataSource apk) throws IOException, ApkFormatException, NoSuchAlgorithmException {
- int maxSdkVersion = mMaxSdkVersion;
-
- ApkUtils.ZipSections zipSections;
- try {
- zipSections = ApkUtils.findZipSections(apk);
- } catch (ZipFormatException e) {
- throw new ApkFormatException("Malformed APK: not a ZIP archive", e);
- }
-
- int minSdkVersion = verifyAndGetMinSdkVersion(apk, zipSections);
-
- Result result = new Result();
- Map> signatureSchemeApkContentDigests =
- new HashMap<>();
-
- // The SUPPORTED_APK_SIG_SCHEME_NAMES contains the mapping from version number to scheme
- // name, but the verifiers use this parameter as the schemes supported by the target SDK
- // range. Since the code below skips signature verification based on max SDK the mapping of
- // supported schemes needs to be modified to ensure the verifiers do not report a stripped
- // signature for an SDK range that does not support that signature version. For instance an
- // APK with V1, V2, and V3 signatures and a max SDK of O would skip the V3 signature
- // verification, but the SUPPORTED_APK_SIG_SCHEME_NAMES contains version 3, so when the V2
- // verification is performed it would see the stripping protection attribute, see that V3
- // is in the list of supported signatures, and report a stripped signature.
- Map supportedSchemeNames = getSupportedSchemeNames(maxSdkVersion);
-
- // Android N and newer attempts to verify APKs using the APK Signing Block, which can
- // include v2 and/or v3 signatures. If none is found, it falls back to JAR signature
- // verification. If the signature is found but does not verify, the APK is rejected.
- Set foundApkSigSchemeIds = new HashSet<>(2);
- if (maxSdkVersion >= AndroidSdkVersion.N) {
- RunnablesExecutor executor = RunnablesExecutor.SINGLE_THREADED;
- // Attempt to verify the APK using v2 signing if necessary. Platforms prior to Android P
- // ignore APK Signature Scheme v3 signatures and always attempt to verify either JAR or
- // APK Signature Scheme v2 signatures. Android P onwards verifies v2 signatures only if
- // no APK Signature Scheme v3 (or newer scheme) signatures were found.
- try {
- ApkSigningBlockUtils.Result v2Result =
- V2SchemeVerifier.verify(
- executor,
- apk,
- zipSections,
- supportedSchemeNames,
- foundApkSigSchemeIds,
- Math.max(minSdkVersion, AndroidSdkVersion.N),
- maxSdkVersion);
- foundApkSigSchemeIds.add(ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2);
- result.mergeFrom(v2Result);
- signatureSchemeApkContentDigests.put(
- ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2,
- getApkContentDigestsFromSigningSchemeResult(v2Result));
- } catch (ApkSigningBlockUtils.SignatureNotFoundException ignored) {
- // v2 signature not required
- }
- if (result.containsErrors()) {
- return result;
- }
- }
-
- return result;
- }
-
- /**
- * Simply retrieve result that contains V1Signature only by ignoring all the verification process.
- *
- * @param apk APK file contents
- * @throws IOException if an I/O error is encountered while reading the APK
- * @throws ApkFormatException if the APK is malformed
- * @throws NoSuchAlgorithmException if the APK's signatures cannot be verified because a
- * required cryptographic algorithm implementation is missing
- */
- private Result retrieveV1Signature(DataSource apk) throws IOException, ApkFormatException, NoSuchAlgorithmException {
- int maxSdkVersion = mMaxSdkVersion;
-
- ApkUtils.ZipSections zipSections;
- try {
- zipSections = ApkUtils.findZipSections(apk);
- } catch (ZipFormatException e) {
- throw new ApkFormatException("Malformed APK: not a ZIP archive", e);
- }
-
- Result result = new Result();
- Map> signatureSchemeApkContentDigests =
- new HashMap<>();
-
- // The SUPPORTED_APK_SIG_SCHEME_NAMES contains the mapping from version number to scheme
- // name, but the verifiers use this parameter as the schemes supported by the target SDK
- // range. Since the code below skips signature verification based on max SDK the mapping of
- // supported schemes needs to be modified to ensure the verifiers do not report a stripped
- // signature for an SDK range that does not support that signature version. For instance an
- // APK with V1, V2, and V3 signatures and a max SDK of O would skip the V3 signature
- // verification, but the SUPPORTED_APK_SIG_SCHEME_NAMES contains version 3, so when the V2
- // verification is performed it would see the stripping protection attribute, see that V3
- // is in the list of supported signatures, and report a stripped signature.
- Map supportedSchemeNames = getSupportedSchemeNames(maxSdkVersion);
-
- // Android N and newer attempts to verify APKs using the APK Signing Block, which can
- // include v2 and/or v3 signatures. If none is found, it falls back to JAR signature
- // verification. If the signature is found but does not verify, the APK is rejected.
- Set foundApkSigSchemeIds = new HashSet<>(2);
-
- int minSdkVersion = verifyAndGetMinSdkVersion(apk, zipSections);
- V1SchemeVerifier.Result v1Result =
- V1SchemeVerifier.verify(
- apk,
- zipSections,
- supportedSchemeNames,
- foundApkSigSchemeIds,
- minSdkVersion,
- maxSdkVersion);
-
- result.mergeFrom(v1Result);
- return result;
- }
-
-
- /**
- * Verifies the APK's signatures and returns the result of verification. The APK can be
- * considered verified iff the result's {@link Result#isVerified()} returns {@code true}.
- * The verification result also includes errors, warnings, and information about signers.
- *
- * @param apk APK file contents
- * @throws IOException if an I/O error is encountered while reading the APK
- * @throws ApkFormatException if the APK is malformed
- * @throws NoSuchAlgorithmException if the APK's signatures cannot be verified because a
- * required cryptographic algorithm implementation is missing
- */
- private Result verify(DataSource apk)
- throws IOException, ApkFormatException, NoSuchAlgorithmException {
- int maxSdkVersion = mMaxSdkVersion;
-
- ApkUtils.ZipSections zipSections;
- try {
- zipSections = ApkUtils.findZipSections(apk);
- } catch (ZipFormatException e) {
- throw new ApkFormatException("Malformed APK: not a ZIP archive", e);
- }
-
- ByteBuffer androidManifest = null;
-
- int minSdkVersion = verifyAndGetMinSdkVersion(apk, zipSections);
-
- Result result = new Result();
- Map> signatureSchemeApkContentDigests =
- new HashMap<>();
-
- // The SUPPORTED_APK_SIG_SCHEME_NAMES contains the mapping from version number to scheme
- // name, but the verifiers use this parameter as the schemes supported by the target SDK
- // range. Since the code below skips signature verification based on max SDK the mapping of
- // supported schemes needs to be modified to ensure the verifiers do not report a stripped
- // signature for an SDK range that does not support that signature version. For instance an
- // APK with V1, V2, and V3 signatures and a max SDK of O would skip the V3 signature
- // verification, but the SUPPORTED_APK_SIG_SCHEME_NAMES contains version 3, so when the V2
- // verification is performed it would see the stripping protection attribute, see that V3
- // is in the list of supported signatures, and report a stripped signature.
- Map supportedSchemeNames = getSupportedSchemeNames(maxSdkVersion);
-
- // Android N and newer attempts to verify APKs using the APK Signing Block, which can
- // include v2 and/or v3 signatures. If none is found, it falls back to JAR signature
- // verification. If the signature is found but does not verify, the APK is rejected.
- Set foundApkSigSchemeIds = new HashSet<>(2);
- if (maxSdkVersion >= AndroidSdkVersion.N) {
- RunnablesExecutor executor = RunnablesExecutor.SINGLE_THREADED;
- // Android P and newer attempts to verify APKs using APK Signature Scheme v3
- if (maxSdkVersion >= AndroidSdkVersion.P) {
- try {
- ApkSigningBlockUtils.Result v3Result =
- V3SchemeVerifier.verify(
- executor,
- apk,
- zipSections,
- Math.max(minSdkVersion, AndroidSdkVersion.P),
- maxSdkVersion);
- foundApkSigSchemeIds.add(ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3);
- result.mergeFrom(v3Result);
- signatureSchemeApkContentDigests.put(
- ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3,
- getApkContentDigestsFromSigningSchemeResult(v3Result));
- } catch (ApkSigningBlockUtils.SignatureNotFoundException ignored) {
- // v3 signature not required
- }
- if (result.containsErrors()) {
- return result;
- }
- }
-
- // Attempt to verify the APK using v2 signing if necessary. Platforms prior to Android P
- // ignore APK Signature Scheme v3 signatures and always attempt to verify either JAR or
- // APK Signature Scheme v2 signatures. Android P onwards verifies v2 signatures only if
- // no APK Signature Scheme v3 (or newer scheme) signatures were found.
- if (minSdkVersion < AndroidSdkVersion.P || foundApkSigSchemeIds.isEmpty()) {
- try {
- ApkSigningBlockUtils.Result v2Result =
- V2SchemeVerifier.verify(
- executor,
- apk,
- zipSections,
- supportedSchemeNames,
- foundApkSigSchemeIds,
- Math.max(minSdkVersion, AndroidSdkVersion.N),
- maxSdkVersion);
- foundApkSigSchemeIds.add(ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2);
- result.mergeFrom(v2Result);
- signatureSchemeApkContentDigests.put(
- ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2,
- getApkContentDigestsFromSigningSchemeResult(v2Result));
- } catch (ApkSigningBlockUtils.SignatureNotFoundException ignored) {
- // v2 signature not required
- }
- if (result.containsErrors()) {
- return result;
- }
- }
-
- // If v4 file is specified, use additional verification on it
- if (mV4SignatureFile != null) {
- final ApkSigningBlockUtils.Result v4Result =
- V4SchemeVerifier.verify(apk, mV4SignatureFile);
- foundApkSigSchemeIds.add(
- ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V4);
- result.mergeFrom(v4Result);
- if (result.containsErrors()) {
- return result;
- }
- }
- }
-
- // Android O and newer requires that APKs targeting security sandbox version 2 and higher
- // are signed using APK Signature Scheme v2 or newer.
- if (maxSdkVersion >= AndroidSdkVersion.O) {
- if (androidManifest == null) {
- androidManifest = getAndroidManifestFromApk(apk, zipSections);
- }
- int targetSandboxVersion =
- getTargetSandboxVersionFromBinaryAndroidManifest(androidManifest.slice());
- if (targetSandboxVersion > 1) {
- if (foundApkSigSchemeIds.isEmpty()) {
- result.addError(
- Issue.NO_SIG_FOR_TARGET_SANDBOX_VERSION,
- targetSandboxVersion);
- }
- }
- }
-
- List cdRecords =
- V1SchemeVerifier.parseZipCentralDirectory(apk, zipSections);
-
- // Attempt to verify the APK using JAR signing if necessary. Platforms prior to Android N
- // ignore APK Signature Scheme v2 signatures and always attempt to verify JAR signatures.
- // Android N onwards verifies JAR signatures only if no APK Signature Scheme v2 (or newer
- // scheme) signatures were found.
- if ((minSdkVersion < AndroidSdkVersion.N) || (foundApkSigSchemeIds.isEmpty())) {
- V1SchemeVerifier.Result v1Result =
- V1SchemeVerifier.verify(
- apk,
- zipSections,
- supportedSchemeNames,
- foundApkSigSchemeIds,
- minSdkVersion,
- maxSdkVersion);
- result.mergeFrom(v1Result);
- signatureSchemeApkContentDigests.put(
- ApkSigningBlockUtils.VERSION_JAR_SIGNATURE_SCHEME,
- getApkContentDigestFromV1SigningScheme(cdRecords, apk, zipSections));
- }
- if (result.containsErrors()) {
- return result;
- }
-
- // Verify the SourceStamp, if found in the APK.
- try {
- CentralDirectoryRecord sourceStampCdRecord = null;
- for (CentralDirectoryRecord cdRecord : cdRecords) {
- if (SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME.equals(
- cdRecord.getName())) {
- sourceStampCdRecord = cdRecord;
- break;
- }
- }
- // If SourceStamp file is found inside the APK, there must be a SourceStamp
- // block in the APK signing block as well.
- if (sourceStampCdRecord != null) {
- byte[] sourceStampCertificateDigest =
- LocalFileRecord.getUncompressedData(
- apk,
- sourceStampCdRecord,
- zipSections.getZipCentralDirectoryOffset());
- ApkSigResult sourceStampResult =
- V2SourceStampVerifier.verify(
- apk,
- zipSections,
- sourceStampCertificateDigest,
- signatureSchemeApkContentDigests,
- Math.max(minSdkVersion, AndroidSdkVersion.R),
- maxSdkVersion);
- result.mergeFrom(sourceStampResult);
- }
- } catch (SignatureNotFoundException ignored) {
- result.addWarning(Issue.SOURCE_STAMP_SIG_MISSING);
- } catch (ZipFormatException e) {
- throw new ApkFormatException("Failed to read APK", e);
- }
- if (result.containsErrors()) {
- return result;
- }
-
- // Check whether v1 and v2 scheme signer identifies match, provided both v1 and v2
- // signatures verified.
- if ((result.isVerifiedUsingV1Scheme()) && (result.isVerifiedUsingV2Scheme())) {
- ArrayList v1Signers =
- new ArrayList<>(result.getV1SchemeSigners());
- ArrayList v2Signers =
- new ArrayList<>(result.getV2SchemeSigners());
- ArrayList v1SignerCerts = new ArrayList<>();
- ArrayList v2SignerCerts = new ArrayList<>();
- for (Result.V1SchemeSignerInfo signer : v1Signers) {
- try {
- v1SignerCerts.add(new ByteArray(signer.getCertificate().getEncoded()));
- } catch (CertificateEncodingException e) {
- throw new IllegalStateException(
- "Failed to encode JAR signer " + signer.getName() + " certs", e);
- }
- }
- for (Result.V2SchemeSignerInfo signer : v2Signers) {
- try {
- v2SignerCerts.add(new ByteArray(signer.getCertificate().getEncoded()));
- } catch (CertificateEncodingException e) {
- throw new IllegalStateException(
- "Failed to encode APK Signature Scheme v2 signer (index: "
- + signer.getIndex() + ") certs",
- e);
- }
- }
-
- for (int i = 0; i < v1SignerCerts.size(); i++) {
- ByteArray v1Cert = v1SignerCerts.get(i);
- if (!v2SignerCerts.contains(v1Cert)) {
- Result.V1SchemeSignerInfo v1Signer = v1Signers.get(i);
- v1Signer.addError(Issue.V2_SIG_MISSING);
- break;
- }
- }
- for (int i = 0; i < v2SignerCerts.size(); i++) {
- ByteArray v2Cert = v2SignerCerts.get(i);
- if (!v1SignerCerts.contains(v2Cert)) {
- Result.V2SchemeSignerInfo v2Signer = v2Signers.get(i);
- v2Signer.addError(Issue.JAR_SIG_MISSING);
- break;
- }
- }
- }
-
- // If there is a v3 scheme signer and an earlier scheme signer, make sure that there is a
- // match, or in the event of signing certificate rotation, that the v1/v2 scheme signer
- // matches the oldest signing certificate in the provided SigningCertificateLineage
- if (result.isVerifiedUsingV3Scheme()
- && (result.isVerifiedUsingV1Scheme() || result.isVerifiedUsingV2Scheme())) {
- SigningCertificateLineage lineage = result.getSigningCertificateLineage();
- X509Certificate oldSignerCert;
- if (result.isVerifiedUsingV1Scheme()) {
- List v1Signers = result.getV1SchemeSigners();
- if (v1Signers.size() != 1) {
- // APK Signature Scheme v3 only supports single-signers, error to sign with
- // multiple and then only one
- result.addError(Issue.V3_SIG_MULTIPLE_PAST_SIGNERS);
- }
- oldSignerCert = v1Signers.get(0).mCertChain.get(0);
- } else {
- List v2Signers = result.getV2SchemeSigners();
- if (v2Signers.size() != 1) {
- // APK Signature Scheme v3 only supports single-signers, error to sign with
- // multiple and then only one
- result.addError(Issue.V3_SIG_MULTIPLE_PAST_SIGNERS);
- }
- oldSignerCert = v2Signers.get(0).mCerts.get(0);
- }
- if (lineage == null) {
- // no signing certificate history with which to contend, just make sure that v3
- // matches previous versions
- List v3Signers = result.getV3SchemeSigners();
- if (v3Signers.size() != 1) {
- // multiple v3 signers should never exist without rotation history, since
- // multiple signers implies a different signer for different platform versions
- result.addError(Issue.V3_SIG_MULTIPLE_SIGNERS);
- }
- try {
- if (!Arrays.equals(oldSignerCert.getEncoded(),
- v3Signers.get(0).mCerts.get(0).getEncoded())) {
- result.addError(Issue.V3_SIG_PAST_SIGNERS_MISMATCH);
- }
- } catch (CertificateEncodingException e) {
- // we just go the encoding for the v1/v2 certs above, so must be v3
- throw new RuntimeException(
- "Failed to encode APK Signature Scheme v3 signer cert", e);
- }
- } else {
- // we have some signing history, make sure that the root of the history is the same
- // as our v1/v2 signer
- try {
- lineage = lineage.getSubLineage(oldSignerCert);
- if (lineage.size() != 1) {
- // the v1/v2 signer was found, but not at the root of the lineage
- result.addError(Issue.V3_SIG_PAST_SIGNERS_MISMATCH);
- }
- } catch (IllegalArgumentException e) {
- // the v1/v2 signer was not found in the lineage
- result.addError(Issue.V3_SIG_PAST_SIGNERS_MISMATCH);
- }
- }
- }
-
-
- // If there is a v4 scheme signer, make sure that their certificates match.
- // The apkDigest field in the v4 signature should match the selected v2/v3.
- if (result.isVerifiedUsingV4Scheme()) {
- List v4Signers = result.getV4SchemeSigners();
- if (v4Signers.size() != 1) {
- result.addError(Issue.V4_SIG_MULTIPLE_SIGNERS);
- }
-
- List digestsFromV4 =
- v4Signers.get(0).getContentDigests();
- if (digestsFromV4.size() != 1) {
- result.addError(Issue.V4_SIG_V2_V3_DIGESTS_MISMATCH);
- }
- final byte[] digestFromV4 = digestsFromV4.get(0).getValue();
-
- if (result.isVerifiedUsingV3Scheme()) {
- List v3Signers = result.getV3SchemeSigners();
- if (v3Signers.size() != 1) {
- result.addError(Issue.V4_SIG_MULTIPLE_SIGNERS);
- }
-
- // Compare certificates.
- checkV4Certificate(v4Signers.get(0).mCerts, v3Signers.get(0).mCerts, result);
-
- // Compare digests.
- final byte[] digestFromV3 = pickBestDigestForV4(
- v3Signers.get(0).getContentDigests());
- if (!Arrays.equals(digestFromV4, digestFromV3)) {
- result.addError(Issue.V4_SIG_V2_V3_DIGESTS_MISMATCH);
- }
- } else if (result.isVerifiedUsingV2Scheme()) {
- List v2Signers = result.getV2SchemeSigners();
- if (v2Signers.size() != 1) {
- result.addError(Issue.V4_SIG_MULTIPLE_SIGNERS);
- }
-
- // Compare certificates.
- checkV4Certificate(v4Signers.get(0).mCerts, v2Signers.get(0).mCerts, result);
-
- // Compare digests.
- final byte[] digestFromV2 = pickBestDigestForV4(
- v2Signers.get(0).getContentDigests());
- if (!Arrays.equals(digestFromV4, digestFromV2)) {
- result.addError(Issue.V4_SIG_V2_V3_DIGESTS_MISMATCH);
- }
- } else {
- throw new RuntimeException("V4 signature must be also verified with V2/V3");
- }
- }
-
- // If the targetSdkVersion has a minimum required signature scheme version then verify
- // that the APK was signed with at least that version.
- try {
- if (androidManifest == null) {
- androidManifest = getAndroidManifestFromApk(apk, zipSections);
- }
- } catch (ApkFormatException e) {
- // If the manifest is not available then skip the minimum signature scheme requirement
- // to support bundle verification.
- }
- if (androidManifest != null) {
- int targetSdkVersion = getTargetSdkVersionFromBinaryAndroidManifest(
- androidManifest.slice());
- int minSchemeVersion = getMinimumSignatureSchemeVersionForTargetSdk(targetSdkVersion);
- // The platform currently only enforces a single minimum signature scheme version, but
- // when later platform versions support another minimum version this will need to be
- // expanded to verify the minimum based on the target and maximum SDK version.
- if (minSchemeVersion > VERSION_JAR_SIGNATURE_SCHEME
- && maxSdkVersion >= targetSdkVersion) {
- switch (minSchemeVersion) {
- case VERSION_APK_SIGNATURE_SCHEME_V2:
- if (result.isVerifiedUsingV2Scheme()) {
- break;
- }
- // Allow this case to fall through to the next as a signature satisfying a
- // later scheme version will also satisfy this requirement.
- case VERSION_APK_SIGNATURE_SCHEME_V3:
- if (result.isVerifiedUsingV3Scheme()) {
- break;
- }
- result.addError(Issue.MIN_SIG_SCHEME_FOR_TARGET_SDK_NOT_MET,
- targetSdkVersion,
- minSchemeVersion);
- }
- }
- }
-
- if (result.containsErrors()) {
- return result;
- }
-
- // Verified
- result.setVerified();
- if (result.isVerifiedUsingV3Scheme()) {
- List v3Signers = result.getV3SchemeSigners();
- result.addSignerCertificate(v3Signers.get(v3Signers.size() - 1).getCertificate());
- } else if (result.isVerifiedUsingV2Scheme()) {
- for (Result.V2SchemeSignerInfo signerInfo : result.getV2SchemeSigners()) {
- result.addSignerCertificate(signerInfo.getCertificate());
- }
- } else if (result.isVerifiedUsingV1Scheme()) {
- for (Result.V1SchemeSignerInfo signerInfo : result.getV1SchemeSigners()) {
- result.addSignerCertificate(signerInfo.getCertificate());
- }
- } else {
- throw new RuntimeException(
- "APK verified, but has not verified using any of v1, v2 or v3 schemes");
- }
-
- return result;
- }
-
- /**
- * Verifies and returns the minimum SDK version, either as provided to the builder or as read
- * from the {@code apk}'s AndroidManifest.xml.
- */
- private int verifyAndGetMinSdkVersion(DataSource apk, ApkUtils.ZipSections zipSections)
- throws ApkFormatException, IOException {
- if (mMinSdkVersion != null) {
- if (mMinSdkVersion < 0) {
- throw new IllegalArgumentException(
- "minSdkVersion must not be negative: " + mMinSdkVersion);
- }
- if ((mMinSdkVersion != null) && (mMinSdkVersion > mMaxSdkVersion)) {
- throw new IllegalArgumentException(
- "minSdkVersion (" + mMinSdkVersion + ") > maxSdkVersion (" + mMaxSdkVersion
- + ")");
- }
- return mMinSdkVersion;
- }
-
- ByteBuffer androidManifest = null;
- // Need to obtain minSdkVersion from the APK's AndroidManifest.xml
- if (androidManifest == null) {
- androidManifest = getAndroidManifestFromApk(apk, zipSections);
- }
- int minSdkVersion =
- ApkUtils.getMinSdkVersionFromBinaryAndroidManifest(androidManifest.slice());
- if (minSdkVersion > mMaxSdkVersion) {
- throw new IllegalArgumentException(
- "minSdkVersion from APK (" + minSdkVersion + ") > maxSdkVersion ("
- + mMaxSdkVersion + ")");
- }
- return minSdkVersion;
- }
-
- /**
- * Returns the mapping of signature scheme version to signature scheme name for all signature
- * schemes starting from V2 supported by the {@code maxSdkVersion}.
- */
- private static Map getSupportedSchemeNames(int maxSdkVersion) {
- Map supportedSchemeNames;
- if (maxSdkVersion >= AndroidSdkVersion.P) {
- supportedSchemeNames = SUPPORTED_APK_SIG_SCHEME_NAMES;
- } else if (maxSdkVersion >= AndroidSdkVersion.N) {
- supportedSchemeNames = new HashMap<>(1);
- supportedSchemeNames.put(ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2,
- SUPPORTED_APK_SIG_SCHEME_NAMES.get(
- ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2));
- } else {
- supportedSchemeNames = Collections.emptyMap();
- }
- return supportedSchemeNames;
- }
-
- /**
- * Verifies the APK's source stamp signature and returns the result of the verification.
- *
- * The APK's source stamp can be considered verified if the result's {@link
- * Result#isVerified} returns {@code true}. The details of the source stamp verification can
- * be obtained from the result's {@link Result#getSourceStampInfo()}} including the success or
- * failure cause from {@link Result.SourceStampInfo#getSourceStampVerificationStatus()}. If the
- * verification fails additional details regarding the failure can be obtained from {@link
- * Result#getAllErrors()}}.
- */
- public Result verifySourceStamp() {
- return verifySourceStamp(null);
- }
-
- /**
- * Verifies the APK's source stamp signature, including verification that the SHA-256 digest of
- * the stamp signing certificate matches the {@code expectedCertDigest}, and returns the result
- * of the verification.
- *
- *
A value of {@code null} for the {@code expectedCertDigest} will verify the source stamp,
- * if present, without verifying the actual source stamp certificate used to sign the source
- * stamp. This can be used to verify an APK contains a properly signed source stamp without
- * verifying a particular signer.
- *
- * @see #verifySourceStamp()
- */
- public Result verifySourceStamp(String expectedCertDigest) {
- Closeable in = null;
- try {
- DataSource apk;
- if (mApkDataSource != null) {
- apk = mApkDataSource;
- } else if (mApkFile != null) {
- RandomAccessFile f = new RandomAccessFile(mApkFile, "r");
- in = f;
- apk = DataSources.asDataSource(f, 0, f.length());
- } else {
- throw new IllegalStateException("APK not provided");
- }
- return verifySourceStamp(apk, expectedCertDigest);
- } catch (IOException e) {
- return createSourceStampResultWithError(
- Result.SourceStampInfo.SourceStampVerificationStatus.VERIFICATION_ERROR,
- Issue.UNEXPECTED_EXCEPTION, e);
- } finally {
- if (in != null) {
- try {
- in.close();
- } catch (IOException ignored) {
- }
- }
- }
- }
-
- /**
- * Verifies the provided {@code apk}'s source stamp signature, including verification of the
- * SHA-256 digest of the stamp signing certificate matches the {@code expectedCertDigest}, and
- * returns the result of the verification.
- *
- * @see #verifySourceStamp(String)
- */
- private Result verifySourceStamp(DataSource apk, String expectedCertDigest) {
- try {
- ApkUtils.ZipSections zipSections = ApkUtils.findZipSections(apk);
- int minSdkVersion = verifyAndGetMinSdkVersion(apk, zipSections);
-
- // Attempt to obtain the source stamp's certificate digest from the APK.
- List cdRecords =
- V1SchemeVerifier.parseZipCentralDirectory(apk, zipSections);
- CentralDirectoryRecord sourceStampCdRecord = null;
- for (CentralDirectoryRecord cdRecord : cdRecords) {
- if (SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME.equals(cdRecord.getName())) {
- sourceStampCdRecord = cdRecord;
- break;
- }
- }
-
- // If the source stamp's certificate digest is not available within the APK then the
- // source stamp cannot be verified; check if a source stamp signing block is in the
- // APK's signature block to determine the appropriate status to return.
- if (sourceStampCdRecord == null) {
- boolean stampSigningBlockFound;
- try {
- ApkSigningBlockUtils.Result result = new ApkSigningBlockUtils.Result(
- ApkSigningBlockUtils.VERSION_SOURCE_STAMP);
- ApkSigningBlockUtils.findSignature(apk, zipSections,
- SourceStampConstants.V2_SOURCE_STAMP_BLOCK_ID, result);
- stampSigningBlockFound = true;
- } catch (ApkSigningBlockUtils.SignatureNotFoundException e) {
- stampSigningBlockFound = false;
- }
- if (stampSigningBlockFound) {
- return createSourceStampResultWithError(
- Result.SourceStampInfo.SourceStampVerificationStatus.STAMP_NOT_VERIFIED,
- Issue.SOURCE_STAMP_SIGNATURE_BLOCK_WITHOUT_CERT_DIGEST);
- } else {
- return createSourceStampResultWithError(
- Result.SourceStampInfo.SourceStampVerificationStatus.STAMP_MISSING,
- Issue.SOURCE_STAMP_CERT_DIGEST_AND_SIG_BLOCK_MISSING);
- }
- }
-
- // Verify that the contents of the source stamp certificate digest match the expected
- // value, if provided.
- byte[] sourceStampCertificateDigest =
- LocalFileRecord.getUncompressedData(
- apk,
- sourceStampCdRecord,
- zipSections.getZipCentralDirectoryOffset());
- if (expectedCertDigest != null) {
- String actualCertDigest = ApkSigningBlockUtils.toHex(sourceStampCertificateDigest);
- if (!expectedCertDigest.equalsIgnoreCase(actualCertDigest)) {
- return createSourceStampResultWithError(
- Result.SourceStampInfo.SourceStampVerificationStatus
- .CERT_DIGEST_MISMATCH,
- Issue.SOURCE_STAMP_EXPECTED_DIGEST_MISMATCH, actualCertDigest,
- expectedCertDigest);
- }
- }
-
- Map> signatureSchemeApkContentDigests =
- new HashMap<>();
- Map supportedSchemeNames = getSupportedSchemeNames(mMaxSdkVersion);
- Set foundApkSigSchemeIds = new HashSet<>(2);
-
- Result result = new Result();
- ApkSigningBlockUtils.Result v3Result = null;
- if (mMaxSdkVersion >= AndroidSdkVersion.P) {
- v3Result = getApkContentDigests(apk, zipSections, foundApkSigSchemeIds,
- supportedSchemeNames, signatureSchemeApkContentDigests,
- VERSION_APK_SIGNATURE_SCHEME_V3,
- Math.max(minSdkVersion, AndroidSdkVersion.P));
- if (v3Result != null && v3Result.containsErrors()) {
- result.mergeFrom(v3Result);
- return mergeSourceStampResult(
- Result.SourceStampInfo.SourceStampVerificationStatus.VERIFICATION_ERROR,
- result);
- }
- }
-
- ApkSigningBlockUtils.Result v2Result = null;
- if (mMaxSdkVersion >= AndroidSdkVersion.N && (minSdkVersion < AndroidSdkVersion.P
- || foundApkSigSchemeIds.isEmpty())) {
- v2Result = getApkContentDigests(apk, zipSections, foundApkSigSchemeIds,
- supportedSchemeNames, signatureSchemeApkContentDigests,
- VERSION_APK_SIGNATURE_SCHEME_V2,
- Math.max(minSdkVersion, AndroidSdkVersion.N));
- if (v2Result != null && v2Result.containsErrors()) {
- result.mergeFrom(v2Result);
- return mergeSourceStampResult(
- Result.SourceStampInfo.SourceStampVerificationStatus.VERIFICATION_ERROR,
- result);
- }
- }
-
- if (minSdkVersion < AndroidSdkVersion.N || foundApkSigSchemeIds.isEmpty()) {
- signatureSchemeApkContentDigests.put(VERSION_JAR_SIGNATURE_SCHEME,
- getApkContentDigestFromV1SigningScheme(cdRecords, apk, zipSections));
- }
-
- ApkSigResult sourceStampResult =
- V2SourceStampVerifier.verify(
- apk,
- zipSections,
- sourceStampCertificateDigest,
- signatureSchemeApkContentDigests,
- minSdkVersion,
- mMaxSdkVersion);
- result.mergeFrom(sourceStampResult);
- // Since the caller is only seeking to verify the source stamp the Result can be marked
- // as verified if the source stamp verification was successful.
- if (sourceStampResult.verified) {
- result.setVerified();
- } else {
- // To prevent APK signature verification with a failed / missing source stamp the
- // source stamp verification will only log warnings; to allow the caller to capture
- // the failure reason treat all warnings as errors.
- result.setWarningsAsErrors(true);
- }
- return result;
- } catch (ApkFormatException | IOException | ZipFormatException e) {
- return createSourceStampResultWithError(
- Result.SourceStampInfo.SourceStampVerificationStatus.VERIFICATION_ERROR,
- Issue.MALFORMED_APK, e);
- } catch (NoSuchAlgorithmException e) {
- return createSourceStampResultWithError(
- Result.SourceStampInfo.SourceStampVerificationStatus.VERIFICATION_ERROR,
- Issue.UNEXPECTED_EXCEPTION, e);
- } catch (SignatureNotFoundException e) {
- return createSourceStampResultWithError(
- Result.SourceStampInfo.SourceStampVerificationStatus.STAMP_NOT_VERIFIED,
- Issue.SOURCE_STAMP_SIG_MISSING);
- }
- }
-
- /**
- * Creates and returns a {@code Result} that can be returned for source stamp verification
- * with the provided source stamp {@code verificationStatus}, and logs an error for the
- * specified {@code issue} and {@code params}.
- */
- private static Result createSourceStampResultWithError(
- Result.SourceStampInfo.SourceStampVerificationStatus verificationStatus, Issue issue,
- Object... params) {
- Result result = new Result();
- result.addError(issue, params);
- return mergeSourceStampResult(verificationStatus, result);
- }
-
- /**
- * Creates a new {@link Result.SourceStampInfo} under the provided {@code result} and sets the
- * source stamp status to the provided {@code verificationStatus}.
- */
- private static Result mergeSourceStampResult(
- Result.SourceStampInfo.SourceStampVerificationStatus verificationStatus,
- Result result) {
- result.mSourceStampInfo = new Result.SourceStampInfo(verificationStatus);
- return result;
- }
-
- /**
- * Obtains the APK content digest(s) and adds them to the provided {@code
- * sigSchemeApkContentDigests}, returning an {@code ApkSigningBlockUtils.Result} that can be
- * merged with a {@code Result} to notify the client of any errors.
- *
- * Note, this method currently only supports signature scheme V2 and V3; to obtain the
- * content digests for V1 signatures use {@link
- * #getApkContentDigestFromV1SigningScheme(List, DataSource, ApkUtils.ZipSections)}. If a
- * signature scheme version other than V2 or V3 is provided a {@code null} value will be
- * returned.
- */
- private ApkSigningBlockUtils.Result getApkContentDigests(DataSource apk,
- ApkUtils.ZipSections zipSections, Set foundApkSigSchemeIds,
- Map supportedSchemeNames,
- Map> sigSchemeApkContentDigests,
- int apkSigSchemeVersion, int minSdkVersion)
- throws IOException, NoSuchAlgorithmException {
- if (!(apkSigSchemeVersion == VERSION_APK_SIGNATURE_SCHEME_V2
- || apkSigSchemeVersion == VERSION_APK_SIGNATURE_SCHEME_V3)) {
- return null;
- }
- ApkSigningBlockUtils.Result result = new ApkSigningBlockUtils.Result(apkSigSchemeVersion);
- SignatureInfo signatureInfo;
- try {
- int sigSchemeBlockId = apkSigSchemeVersion == VERSION_APK_SIGNATURE_SCHEME_V3
- ? V3SchemeConstants.APK_SIGNATURE_SCHEME_V3_BLOCK_ID
- : V2SchemeConstants.APK_SIGNATURE_SCHEME_V2_BLOCK_ID;
- signatureInfo = ApkSigningBlockUtils.findSignature(apk, zipSections,
- sigSchemeBlockId, result);
- } catch (ApkSigningBlockUtils.SignatureNotFoundException e) {
- return null;
- }
- foundApkSigSchemeIds.add(apkSigSchemeVersion);
-
- Set contentDigestsToVerify = new HashSet<>(1);
- if (apkSigSchemeVersion == VERSION_APK_SIGNATURE_SCHEME_V2) {
- V2SchemeVerifier.parseSigners(signatureInfo.signatureBlock,
- contentDigestsToVerify, supportedSchemeNames,
- foundApkSigSchemeIds, minSdkVersion, mMaxSdkVersion, result);
- } else {
- V3SchemeVerifier.parseSigners(signatureInfo.signatureBlock,
- contentDigestsToVerify, result);
- }
- Map apkContentDigests = new EnumMap<>(
- ContentDigestAlgorithm.class);
- for (ApkSigningBlockUtils.Result.SignerInfo signerInfo : result.signers) {
- for (ApkSigningBlockUtils.Result.SignerInfo.ContentDigest contentDigest :
- signerInfo.contentDigests) {
- SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.findById(
- contentDigest.getSignatureAlgorithmId());
- if (signatureAlgorithm == null) {
- continue;
- }
- apkContentDigests.put(signatureAlgorithm.getContentDigestAlgorithm(),
- contentDigest.getValue());
- }
- }
- sigSchemeApkContentDigests.put(apkSigSchemeVersion, apkContentDigests);
- return result;
- }
-
- private static void checkV4Certificate(List v4Certs,
- List v2v3Certs, Result result) {
- try {
- byte[] v4Cert = v4Certs.get(0).getEncoded();
- byte[] cert = v2v3Certs.get(0).getEncoded();
- if (!Arrays.equals(cert, v4Cert)) {
- result.addError(Issue.V4_SIG_V2_V3_SIGNERS_MISMATCH);
- }
- } catch (CertificateEncodingException e) {
- throw new RuntimeException("Failed to encode APK signer cert", e);
- }
- }
-
- private static byte[] pickBestDigestForV4(
- List contentDigests) {
- Map apkContentDigests = new HashMap<>();
- collectApkContentDigests(contentDigests, apkContentDigests);
- return ApkSigningBlockUtils.pickBestDigestForV4(apkContentDigests);
- }
-
- private static Map getApkContentDigestsFromSigningSchemeResult(
- ApkSigningBlockUtils.Result apkSigningSchemeResult) {
- Map apkContentDigests = new HashMap<>();
- for (ApkSigningBlockUtils.Result.SignerInfo signerInfo : apkSigningSchemeResult.signers) {
- collectApkContentDigests(signerInfo.contentDigests, apkContentDigests);
- }
- return apkContentDigests;
- }
-
- private static Map getApkContentDigestFromV1SigningScheme(
- List cdRecords,
- DataSource apk,
- ApkUtils.ZipSections zipSections)
- throws IOException, ApkFormatException {
- CentralDirectoryRecord manifestCdRecord = null;
- Map v1ContentDigest = new EnumMap<>(
- ContentDigestAlgorithm.class);
- for (CentralDirectoryRecord cdRecord : cdRecords) {
- if (MANIFEST_ENTRY_NAME.equals(cdRecord.getName())) {
- manifestCdRecord = cdRecord;
- break;
- }
- }
- if (manifestCdRecord == null) {
- // No JAR signing manifest file found. For SourceStamp verification, returning an empty
- // digest is enough since this would affect the final digest signed by the stamp, and
- // thus an empty digest will invalidate that signature.
- return v1ContentDigest;
- }
- try {
- byte[] manifestBytes =
- LocalFileRecord.getUncompressedData(
- apk, manifestCdRecord, zipSections.getZipCentralDirectoryOffset());
- v1ContentDigest.put(
- ContentDigestAlgorithm.SHA256, computeSha256DigestBytes(manifestBytes));
- return v1ContentDigest;
- } catch (ZipFormatException e) {
- throw new ApkFormatException("Failed to read APK", e);
- }
- }
-
- private static void collectApkContentDigests(
- List contentDigests,
- Map apkContentDigests) {
- for (ApkSigningBlockUtils.Result.SignerInfo.ContentDigest contentDigest : contentDigests) {
- SignatureAlgorithm signatureAlgorithm =
- SignatureAlgorithm.findById(contentDigest.getSignatureAlgorithmId());
- if (signatureAlgorithm == null) {
- continue;
- }
- ContentDigestAlgorithm contentDigestAlgorithm =
- signatureAlgorithm.getContentDigestAlgorithm();
- apkContentDigests.put(contentDigestAlgorithm, contentDigest.getValue());
- }
-
- }
-
- private static ByteBuffer getAndroidManifestFromApk(
- DataSource apk, ApkUtils.ZipSections zipSections)
- throws IOException, ApkFormatException {
- List cdRecords =
- V1SchemeVerifier.parseZipCentralDirectory(apk, zipSections);
- try {
- return ApkSigner.getAndroidManifestFromApk(
- cdRecords,
- apk.slice(0, zipSections.getZipCentralDirectoryOffset()));
- } catch (ZipFormatException e) {
- throw new ApkFormatException("Failed to read AndroidManifest.xml", e);
- }
- }
-
- private static int getMinimumSignatureSchemeVersionForTargetSdk(int targetSdkVersion) {
- if (targetSdkVersion >= AndroidSdkVersion.R) {
- return VERSION_APK_SIGNATURE_SCHEME_V2;
- }
- return VERSION_JAR_SIGNATURE_SCHEME;
- }
-
- /**
- * Result of verifying an APKs signatures. The APK can be considered verified iff
- * {@link #isVerified()} returns {@code true}.
- */
- public static class Result {
- private final List mErrors = new ArrayList<>();
- private final List mWarnings = new ArrayList<>();
- private final List mSignerCerts = new ArrayList<>();
- private final List mV1SchemeSigners = new ArrayList<>();
- private final List mV1SchemeIgnoredSigners = new ArrayList<>();
- private final List mV2SchemeSigners = new ArrayList<>();
- private final List mV3SchemeSigners = new ArrayList<>();
- private final List mV4SchemeSigners = new ArrayList<>();
- private SourceStampInfo mSourceStampInfo;
-
- private boolean mVerified;
- private boolean mVerifiedUsingV1Scheme;
- private boolean mVerifiedUsingV2Scheme;
- private boolean mVerifiedUsingV3Scheme;
- private boolean mVerifiedUsingV4Scheme;
- private boolean mSourceStampVerified;
- private boolean mWarningsAsErrors;
- private SigningCertificateLineage mSigningCertificateLineage;
-
- /**
- * Returns {@code true} if the APK's signatures verified.
- */
- public boolean isVerified() {
- return mVerified;
- }
-
- private void setVerified() {
- mVerified = true;
- }
-
- /**
- * Returns {@code true} if the APK's JAR signatures verified.
- */
- public boolean isVerifiedUsingV1Scheme() {
- return mVerifiedUsingV1Scheme;
- }
-
- /**
- * Returns {@code true} if the APK's APK Signature Scheme v2 signatures verified.
- */
- public boolean isVerifiedUsingV2Scheme() {
- return mVerifiedUsingV2Scheme;
- }
-
- /**
- * Returns {@code true} if the APK's APK Signature Scheme v3 signature verified.
- */
- public boolean isVerifiedUsingV3Scheme() {
- return mVerifiedUsingV3Scheme;
- }
-
- /**
- * Returns {@code true} if the APK's APK Signature Scheme v4 signature verified.
- */
- public boolean isVerifiedUsingV4Scheme() {
- return mVerifiedUsingV4Scheme;
- }
-
- /**
- * Returns {@code true} if the APK's SourceStamp signature verified.
- */
- public boolean isSourceStampVerified() {
- return mSourceStampVerified;
- }
-
- /**
- * Returns the verified signers' certificates, one per signer.
- */
- public List getSignerCertificates() {
- return mSignerCerts;
- }
-
- private void addSignerCertificate(X509Certificate cert) {
- mSignerCerts.add(cert);
- }
-
- /**
- * Returns information about JAR signers associated with the APK's signature. These are the
- * signers used by Android.
- *
- * @see #getV1SchemeIgnoredSigners()
- */
- public List getV1SchemeSigners() {
- return mV1SchemeSigners;
- }
-
- /**
- * Returns information about JAR signers ignored by the APK's signature verification
- * process. These signers are ignored by Android. However, each signer's errors or warnings
- * will contain information about why they are ignored.
- *
- * @see #getV1SchemeSigners()
- */
- public List getV1SchemeIgnoredSigners() {
- return mV1SchemeIgnoredSigners;
- }
-
- /**
- * Returns information about APK Signature Scheme v2 signers associated with the APK's
- * signature.
- */
- public List getV2SchemeSigners() {
- return mV2SchemeSigners;
- }
-
- /**
- * Returns information about APK Signature Scheme v3 signers associated with the APK's
- * signature.
- *
- * Multiple signers represent different targeted platform versions, not
- * a signing identity of multiple signers. APK Signature Scheme v3 only supports single
- * signer identities.
- */
- public List getV3SchemeSigners() {
- return mV3SchemeSigners;
- }
-
- private List getV4SchemeSigners() {
- return mV4SchemeSigners;
- }
-
- /**
- * Returns information about SourceStamp associated with the APK's signature.
- */
- public SourceStampInfo getSourceStampInfo() {
- return mSourceStampInfo;
- }
-
- /**
- * Returns the combined SigningCertificateLineage associated with this APK's APK Signature
- * Scheme v3 signing block.
- */
- public SigningCertificateLineage getSigningCertificateLineage() {
- return mSigningCertificateLineage;
- }
-
- void addError(Issue msg, Object... parameters) {
- mErrors.add(new IssueWithParams(msg, parameters));
- }
-
- void addWarning(Issue msg, Object... parameters) {
- mWarnings.add(new IssueWithParams(msg, parameters));
- }
-
- /**
- * Sets whether warnings should be treated as errors.
- */
- void setWarningsAsErrors(boolean value) {
- mWarningsAsErrors = value;
- }
-
- /**
- * Returns errors encountered while verifying the APK's signatures.
- */
- public List getErrors() {
- if (!mWarningsAsErrors) {
- return mErrors;
- } else {
- List allErrors = new ArrayList<>();
- allErrors.addAll(mErrors);
- allErrors.addAll(mWarnings);
- return allErrors;
- }
- }
-
- /**
- * Returns warnings encountered while verifying the APK's signatures.
- */
- public List getWarnings() {
- return mWarnings;
- }
-
- private void mergeFrom(V1SchemeVerifier.Result source) {
- mVerifiedUsingV1Scheme = source.verified;
- mErrors.addAll(source.getErrors());
- mWarnings.addAll(source.getWarnings());
- for (V1SchemeVerifier.Result.SignerInfo signer : source.signers) {
- mV1SchemeSigners.add(new V1SchemeSignerInfo(signer));
- }
- for (V1SchemeVerifier.Result.SignerInfo signer : source.ignoredSigners) {
- mV1SchemeIgnoredSigners.add(new V1SchemeSignerInfo(signer));
- }
- }
-
- private void mergeFrom(ApkSigResult source) {
- switch (source.signatureSchemeVersion) {
- case ApkSigningBlockUtils.VERSION_SOURCE_STAMP:
- mSourceStampVerified = source.verified;
- if (!source.mSigners.isEmpty()) {
- mSourceStampInfo = new SourceStampInfo(source.mSigners.get(0));
- }
- break;
- default:
- throw new IllegalArgumentException(
- "Unknown ApkSigResult Signing Block Scheme Id "
- + source.signatureSchemeVersion);
- }
- }
-
- private void mergeFrom(ApkSigningBlockUtils.Result source) {
- switch (source.signatureSchemeVersion) {
- case ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2:
- mVerifiedUsingV2Scheme = source.verified;
- for (ApkSigningBlockUtils.Result.SignerInfo signer : source.signers) {
- mV2SchemeSigners.add(new V2SchemeSignerInfo(signer));
- }
- break;
- case ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3:
- mVerifiedUsingV3Scheme = source.verified;
- for (ApkSigningBlockUtils.Result.SignerInfo signer : source.signers) {
- mV3SchemeSigners.add(new V3SchemeSignerInfo(signer));
- }
- mSigningCertificateLineage = source.signingCertificateLineage;
- break;
- case ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V4:
- mVerifiedUsingV4Scheme = source.verified;
- for (ApkSigningBlockUtils.Result.SignerInfo signer : source.signers) {
- mV4SchemeSigners.add(new V4SchemeSignerInfo(signer));
- }
- break;
- case ApkSigningBlockUtils.VERSION_SOURCE_STAMP:
- mSourceStampVerified = source.verified;
- if (!source.signers.isEmpty()) {
- mSourceStampInfo = new SourceStampInfo(source.signers.get(0));
- }
- break;
- default:
- throw new IllegalArgumentException("Unknown Signing Block Scheme Id");
- }
- }
-
- /**
- * Returns {@code true} if an error was encountered while verifying the APK. Any error
- * prevents the APK from being considered verified.
- */
- public boolean containsErrors() {
- if (!mErrors.isEmpty()) {
- return true;
- }
- if (mWarningsAsErrors && !mWarnings.isEmpty()) {
- return true;
- }
- if (!mV1SchemeSigners.isEmpty()) {
- for (V1SchemeSignerInfo signer : mV1SchemeSigners) {
- if (signer.containsErrors()) {
- return true;
- }
- if (mWarningsAsErrors && !signer.getWarnings().isEmpty()) {
- return true;
- }
- }
- }
- if (!mV2SchemeSigners.isEmpty()) {
- for (V2SchemeSignerInfo signer : mV2SchemeSigners) {
- if (signer.containsErrors()) {
- return true;
- }
- if (mWarningsAsErrors && !signer.getWarnings().isEmpty()) {
- return true;
- }
- }
- }
- if (!mV3SchemeSigners.isEmpty()) {
- for (V3SchemeSignerInfo signer : mV3SchemeSigners) {
- if (signer.containsErrors()) {
- return true;
- }
- if (mWarningsAsErrors && !signer.getWarnings().isEmpty()) {
- return true;
- }
- }
- }
- if (mSourceStampInfo != null) {
- if (mSourceStampInfo.containsErrors()) {
- return true;
- }
- if (mWarningsAsErrors && !mSourceStampInfo.getWarnings().isEmpty()) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Returns all errors for this result, including any errors from signature scheme signers
- * and the source stamp.
- */
- public List getAllErrors() {
- List errors = new ArrayList<>();
- errors.addAll(mErrors);
- if (mWarningsAsErrors) {
- errors.addAll(mWarnings);
- }
- if (!mV1SchemeSigners.isEmpty()) {
- for (V1SchemeSignerInfo signer : mV1SchemeSigners) {
- errors.addAll(signer.mErrors);
- if (mWarningsAsErrors) {
- errors.addAll(signer.getWarnings());
- }
- }
- }
- if (!mV2SchemeSigners.isEmpty()) {
- for (V2SchemeSignerInfo signer : mV2SchemeSigners) {
- errors.addAll(signer.mErrors);
- if (mWarningsAsErrors) {
- errors.addAll(signer.getWarnings());
- }
- }
- }
- if (!mV3SchemeSigners.isEmpty()) {
- for (V3SchemeSignerInfo signer : mV3SchemeSigners) {
- errors.addAll(signer.mErrors);
- if (mWarningsAsErrors) {
- errors.addAll(signer.getWarnings());
- }
- }
- }
- if (mSourceStampInfo != null) {
- errors.addAll(mSourceStampInfo.getErrors());
- if (mWarningsAsErrors) {
- errors.addAll(mSourceStampInfo.getWarnings());
- }
- }
- return errors;
- }
-
- /**
- * Information about a JAR signer associated with the APK's signature.
- */
- public static class V1SchemeSignerInfo {
- private final String mName;
- private final List mCertChain;
- private final String mSignatureBlockFileName;
- private final String mSignatureFileName;
-
- private final List mErrors;
- private final List mWarnings;
-
- private V1SchemeSignerInfo(V1SchemeVerifier.Result.SignerInfo result) {
- mName = result.name;
- mCertChain = result.certChain;
- mSignatureBlockFileName = result.signatureBlockFileName;
- mSignatureFileName = result.signatureFileName;
- mErrors = result.getErrors();
- mWarnings = result.getWarnings();
- }
-
- /**
- * Returns a user-friendly name of the signer.
- */
- public String getName() {
- return mName;
- }
-
- /**
- * Returns the name of the JAR entry containing this signer's JAR signature block file.
- */
- public String getSignatureBlockFileName() {
- return mSignatureBlockFileName;
- }
-
- /**
- * Returns the name of the JAR entry containing this signer's JAR signature file.
- */
- public String getSignatureFileName() {
- return mSignatureFileName;
- }
-
- /**
- * Returns this signer's signing certificate or {@code null} if not available. The
- * certificate is guaranteed to be available if no errors were encountered during
- * verification (see {@link #containsErrors()}.
- *
- * This certificate contains the signer's public key.
- */
- public X509Certificate getCertificate() {
- return mCertChain.isEmpty() ? null : mCertChain.get(0);
- }
-
- /**
- * Returns the certificate chain for the signer's public key. The certificate containing
- * the public key is first, followed by the certificate (if any) which issued the
- * signing certificate, and so forth. An empty list may be returned if an error was
- * encountered during verification (see {@link #containsErrors()}).
- */
- public List getCertificateChain() {
- return mCertChain;
- }
-
- /**
- * Returns {@code true} if an error was encountered while verifying this signer's JAR
- * signature. Any error prevents the signer's signature from being considered verified.
- */
- public boolean containsErrors() {
- return !mErrors.isEmpty();
- }
-
- /**
- * Returns errors encountered while verifying this signer's JAR signature. Any error
- * prevents the signer's signature from being considered verified.
- */
- public List getErrors() {
- return mErrors;
- }
-
- /**
- * Returns warnings encountered while verifying this signer's JAR signature. Warnings
- * do not prevent the signer's signature from being considered verified.
- */
- public List getWarnings() {
- return mWarnings;
- }
-
- private void addError(Issue msg, Object... parameters) {
- mErrors.add(new IssueWithParams(msg, parameters));
- }
- }
-
- /**
- * Information about an APK Signature Scheme v2 signer associated with the APK's signature.
- */
- public static class V2SchemeSignerInfo {
- private final int mIndex;
- private final List mCerts;
-
- private final List mErrors;
- private final List mWarnings;
- private final List
- mContentDigests;
-
- private V2SchemeSignerInfo(ApkSigningBlockUtils.Result.SignerInfo result) {
- mIndex = result.index;
- mCerts = result.certs;
- mErrors = result.getErrors();
- mWarnings = result.getWarnings();
- mContentDigests = result.contentDigests;
- }
-
- /**
- * Returns this signer's {@code 0}-based index in the list of signers contained in the
- * APK's APK Signature Scheme v2 signature.
- */
- public int getIndex() {
- return mIndex;
- }
-
- /**
- * Returns this signer's signing certificate or {@code null} if not available. The
- * certificate is guaranteed to be available if no errors were encountered during
- * verification (see {@link #containsErrors()}.
- *
- * This certificate contains the signer's public key.
- */
- public X509Certificate getCertificate() {
- return mCerts.isEmpty() ? null : mCerts.get(0);
- }
-
- /**
- * Returns this signer's certificates. The first certificate is for the signer's public
- * key. An empty list may be returned if an error was encountered during verification
- * (see {@link #containsErrors()}).
- */
- public List getCertificates() {
- return mCerts;
- }
-
- private void addError(Issue msg, Object... parameters) {
- mErrors.add(new IssueWithParams(msg, parameters));
- }
-
- public boolean containsErrors() {
- return !mErrors.isEmpty();
- }
-
- public List getErrors() {
- return mErrors;
- }
-
- public List getWarnings() {
- return mWarnings;
- }
-
- public List getContentDigests() {
- return mContentDigests;
- }
- }
-
- /**
- * Information about an APK Signature Scheme v3 signer associated with the APK's signature.
- */
- public static class V3SchemeSignerInfo {
- private final int mIndex;
- private final List mCerts;
-
- private final List mErrors;
- private final List mWarnings;
- private final List
- mContentDigests;
-
- private V3SchemeSignerInfo(ApkSigningBlockUtils.Result.SignerInfo result) {
- mIndex = result.index;
- mCerts = result.certs;
- mErrors = result.getErrors();
- mWarnings = result.getWarnings();
- mContentDigests = result.contentDigests;
- }
-
- /**
- * Returns this signer's {@code 0}-based index in the list of signers contained in the
- * APK's APK Signature Scheme v3 signature.
- */
- public int getIndex() {
- return mIndex;
- }
-
- /**
- * Returns this signer's signing certificate or {@code null} if not available. The
- * certificate is guaranteed to be available if no errors were encountered during
- * verification (see {@link #containsErrors()}.
- *
- * This certificate contains the signer's public key.
- */
- public X509Certificate getCertificate() {
- return mCerts.isEmpty() ? null : mCerts.get(0);
- }
-
- /**
- * Returns this signer's certificates. The first certificate is for the signer's public
- * key. An empty list may be returned if an error was encountered during verification
- * (see {@link #containsErrors()}).
- */
- public List getCertificates() {
- return mCerts;
- }
-
- public boolean containsErrors() {
- return !mErrors.isEmpty();
- }
-
- public List getErrors() {
- return mErrors;
- }
-
- public List getWarnings() {
- return mWarnings;
- }
-
- public List getContentDigests() {
- return mContentDigests;
- }
- }
-
- /**
- * Information about an APK Signature Scheme V4 signer associated with the APK's
- * signature.
- */
- public static class V4SchemeSignerInfo {
- private final int mIndex;
- private final List mCerts;
-
- private final List mErrors;
- private final List mWarnings;
- private final List
- mContentDigests;
-
- private V4SchemeSignerInfo(ApkSigningBlockUtils.Result.SignerInfo result) {
- mIndex = result.index;
- mCerts = result.certs;
- mErrors = result.getErrors();
- mWarnings = result.getWarnings();
- mContentDigests = result.contentDigests;
- }
-
- /**
- * Returns this signer's {@code 0}-based index in the list of signers contained in the
- * APK's APK Signature Scheme v3 signature.
- */
- public int getIndex() {
- return mIndex;
- }
-
- /**
- * Returns this signer's signing certificate or {@code null} if not available. The
- * certificate is guaranteed to be available if no errors were encountered during
- * verification (see {@link #containsErrors()}.
- *
- * This certificate contains the signer's public key.
- */
- public X509Certificate getCertificate() {
- return mCerts.isEmpty() ? null : mCerts.get(0);
- }
-
- /**
- * Returns this signer's certificates. The first certificate is for the signer's public
- * key. An empty list may be returned if an error was encountered during verification
- * (see {@link #containsErrors()}).
- */
- public List getCertificates() {
- return mCerts;
- }
-
- public boolean containsErrors() {
- return !mErrors.isEmpty();
- }
-
- public List getErrors() {
- return mErrors;
- }
-
- public List getWarnings() {
- return mWarnings;
- }
-
- public List getContentDigests() {
- return mContentDigests;
- }
- }
-
- /**
- * Information about SourceStamp associated with the APK's signature.
- */
- public static class SourceStampInfo {
- public enum SourceStampVerificationStatus {
- /** The stamp is present and was successfully verified. */
- STAMP_VERIFIED,
- /** The stamp is present but failed verification. */
- STAMP_VERIFICATION_FAILED,
- /** The expected cert digest did not match the digest in the APK. */
- CERT_DIGEST_MISMATCH,
- /** The stamp is not present at all. */
- STAMP_MISSING,
- /** The stamp is at least partially present, but was not able to be verified. */
- STAMP_NOT_VERIFIED,
- /** The stamp was not able to be verified due to an unexpected error. */
- VERIFICATION_ERROR
- }
-
- private final List mCertificates;
- private final List mCertificateLineage;
-
- private final List mErrors;
- private final List mWarnings;
-
- private final SourceStampVerificationStatus mSourceStampVerificationStatus;
-
- private SourceStampInfo(ApkSignerInfo result) {
- mCertificates = result.certs;
- mCertificateLineage = result.certificateLineage;
- mErrors = ApkVerificationIssueAdapter.getIssuesFromVerificationIssues(
- result.getErrors());
- mWarnings = ApkVerificationIssueAdapter.getIssuesFromVerificationIssues(
- result.getWarnings());
- if (mErrors.isEmpty() && mWarnings.isEmpty()) {
- mSourceStampVerificationStatus = SourceStampVerificationStatus.STAMP_VERIFIED;
- } else {
- mSourceStampVerificationStatus =
- SourceStampVerificationStatus.STAMP_VERIFICATION_FAILED;
- }
- }
-
- SourceStampInfo(SourceStampVerificationStatus sourceStampVerificationStatus) {
- mCertificates = Collections.emptyList();
- mCertificateLineage = Collections.emptyList();
- mErrors = Collections.emptyList();
- mWarnings = Collections.emptyList();
- mSourceStampVerificationStatus = sourceStampVerificationStatus;
- }
-
- /**
- * Returns the SourceStamp's signing certificate or {@code null} if not available. The
- * certificate is guaranteed to be available if no errors were encountered during
- * verification (see {@link #containsErrors()}.
- *
- * This certificate contains the SourceStamp's public key.
- */
- public X509Certificate getCertificate() {
- return mCertificates.isEmpty() ? null : mCertificates.get(0);
- }
-
- /**
- * Returns a list containing all of the certificates in the stamp certificate lineage.
- */
- public List getCertificatesInLineage() {
- return mCertificateLineage;
- }
-
- public boolean containsErrors() {
- return !mErrors.isEmpty();
- }
-
- public List getErrors() {
- return mErrors;
- }
-
- public List getWarnings() {
- return mWarnings;
- }
-
- /**
- * Returns the reason for any source stamp verification failures, or {@code
- * STAMP_VERIFIED} if the source stamp was successfully verified.
- */
- public SourceStampVerificationStatus getSourceStampVerificationStatus() {
- return mSourceStampVerificationStatus;
- }
- }
- }
-
- /**
- * Error or warning encountered while verifying an APK's signatures.
- */
- public enum Issue {
-
- /**
- * APK is not JAR-signed.
- */
- JAR_SIG_NO_SIGNATURES("No JAR signatures"),
-
- /**
- * APK does not contain any entries covered by JAR signatures.
- */
- JAR_SIG_NO_SIGNED_ZIP_ENTRIES("No JAR entries covered by JAR signatures"),
-
- /**
- * APK contains multiple entries with the same name.
- *
- *
- * - Parameter 1: name ({@code String})
- *
- */
- JAR_SIG_DUPLICATE_ZIP_ENTRY("Duplicate entry: %1$s"),
-
- /**
- * JAR manifest contains a section with a duplicate name.
- *
- *
- * - Parameter 1: section name ({@code String})
- *
- */
- JAR_SIG_DUPLICATE_MANIFEST_SECTION("Duplicate section in META-INF/MANIFEST.MF: %1$s"),
-
- /**
- * JAR manifest contains a section without a name.
- *
- *
- * - Parameter 1: section index (1-based) ({@code Integer})
- *
- */
- JAR_SIG_UNNNAMED_MANIFEST_SECTION(
- "Malformed META-INF/MANIFEST.MF: invidual section #%1$d does not have a name"),
-
- /**
- * JAR signature file contains a section without a name.
- *
- *
- * - Parameter 1: signature file name ({@code String})
- * - Parameter 2: section index (1-based) ({@code Integer})
- *
- */
- JAR_SIG_UNNNAMED_SIG_FILE_SECTION(
- "Malformed %1$s: invidual section #%2$d does not have a name"),
-
- /** APK is missing the JAR manifest entry (META-INF/MANIFEST.MF). */
- JAR_SIG_NO_MANIFEST("Missing META-INF/MANIFEST.MF"),
-
- /**
- * JAR manifest references an entry which is not there in the APK.
- *
- *
- * - Parameter 1: entry name ({@code String})
- *
- */
- JAR_SIG_MISSING_ZIP_ENTRY_REFERENCED_IN_MANIFEST(
- "%1$s entry referenced by META-INF/MANIFEST.MF not found in the APK"),
-
- /**
- * JAR manifest does not list a digest for the specified entry.
- *
- *
- * - Parameter 1: entry name ({@code String})
- *
- */
- JAR_SIG_NO_ZIP_ENTRY_DIGEST_IN_MANIFEST("No digest for %1$s in META-INF/MANIFEST.MF"),
-
- /**
- * JAR signature does not list a digest for the specified entry.
- *
- *
- * - Parameter 1: entry name ({@code String})
- * - Parameter 2: signature file name ({@code String})
- *
- */
- JAR_SIG_NO_ZIP_ENTRY_DIGEST_IN_SIG_FILE("No digest for %1$s in %2$s"),
-
- /**
- * The specified JAR entry is not covered by JAR signature.
- *
- *
- * - Parameter 1: entry name ({@code String})
- *
- */
- JAR_SIG_ZIP_ENTRY_NOT_SIGNED("%1$s entry not signed"),
-
- /**
- * JAR signature uses different set of signers to protect the two specified ZIP entries.
- *
- *
- * - Parameter 1: first entry name ({@code String})
- * - Parameter 2: first entry signer names ({@code List})
- * - Parameter 3: second entry name ({@code String})
- * - Parameter 4: second entry signer names ({@code List})
- *
- */
- JAR_SIG_ZIP_ENTRY_SIGNERS_MISMATCH(
- "Entries %1$s and %3$s are signed with different sets of signers"
- + " : <%2$s> vs <%4$s>"),
-
- /**
- * Digest of the specified ZIP entry's data does not match the digest expected by the JAR
- * signature.
- *
- *
- * - Parameter 1: entry name ({@code String})
- * - Parameter 2: digest algorithm (e.g., SHA-256) ({@code String})
- * - Parameter 3: name of the entry in which the expected digest is specified
- * ({@code String})
- * - Parameter 4: base64-encoded actual digest ({@code String})
- * - Parameter 5: base64-encoded expected digest ({@code String})
- *
- */
- JAR_SIG_ZIP_ENTRY_DIGEST_DID_NOT_VERIFY(
- "%2$s digest of %1$s does not match the digest specified in %3$s"
- + ". Expected: <%5$s>, actual: <%4$s>"),
-
- /**
- * Digest of the JAR manifest main section did not verify.
- *
- *
- * - Parameter 1: digest algorithm (e.g., SHA-256) ({@code String})
- * - Parameter 2: name of the entry in which the expected digest is specified
- * ({@code String})
- * - Parameter 3: base64-encoded actual digest ({@code String})
- * - Parameter 4: base64-encoded expected digest ({@code String})
- *
- */
- JAR_SIG_MANIFEST_MAIN_SECTION_DIGEST_DID_NOT_VERIFY(
- "%1$s digest of META-INF/MANIFEST.MF main section does not match the digest"
- + " specified in %2$s. Expected: <%4$s>, actual: <%3$s>"),
-
- /**
- * Digest of the specified JAR manifest section does not match the digest expected by the
- * JAR signature.
- *
- *
- * - Parameter 1: section name ({@code String})
- * - Parameter 2: digest algorithm (e.g., SHA-256) ({@code String})
- * - Parameter 3: name of the signature file in which the expected digest is specified
- * ({@code String})
- * - Parameter 4: base64-encoded actual digest ({@code String})
- * - Parameter 5: base64-encoded expected digest ({@code String})
- *
- */
- JAR_SIG_MANIFEST_SECTION_DIGEST_DID_NOT_VERIFY(
- "%2$s digest of META-INF/MANIFEST.MF section for %1$s does not match the digest"
- + " specified in %3$s. Expected: <%5$s>, actual: <%4$s>"),
-
- /**
- * JAR signature file does not contain the whole-file digest of the JAR manifest file. The
- * digest speeds up verification of JAR signature.
- *
- *
- * - Parameter 1: name of the signature file ({@code String})
- *
- */
- JAR_SIG_NO_MANIFEST_DIGEST_IN_SIG_FILE(
- "%1$s does not specify digest of META-INF/MANIFEST.MF"
- + ". This slows down verification."),
-
- /**
- * APK is signed using APK Signature Scheme v2 or newer, but JAR signature file does not
- * contain protections against stripping of these newer scheme signatures.
- *
- *
- * - Parameter 1: name of the signature file ({@code String})
- *
- */
- JAR_SIG_NO_APK_SIG_STRIP_PROTECTION(
- "APK is signed using APK Signature Scheme v2 but these signatures may be stripped"
- + " without being detected because %1$s does not contain anti-stripping"
- + " protections."),
-
- /**
- * JAR signature of the signer is missing a file/entry.
- *
- *
- * - Parameter 1: name of the encountered file ({@code String})
- * - Parameter 2: name of the missing file ({@code String})
- *
- */
- JAR_SIG_MISSING_FILE("Partial JAR signature. Found: %1$s, missing: %2$s"),
-
- /**
- * An exception was encountered while verifying JAR signature contained in a signature block
- * against the signature file.
- *
- *
- * - Parameter 1: name of the signature block file ({@code String})
- * - Parameter 2: name of the signature file ({@code String})
- * - Parameter 3: exception ({@code Throwable})
- *
- */
- JAR_SIG_VERIFY_EXCEPTION("Failed to verify JAR signature %1$s against %2$s: %3$s"),
-
- /**
- * JAR signature contains unsupported digest algorithm.
- *
- *
- * - Parameter 1: name of the signature block file ({@code String})
- * - Parameter 2: digest algorithm OID ({@code String})
- * - Parameter 3: signature algorithm OID ({@code String})
- * - Parameter 4: API Levels on which this combination of algorithms is not supported
- * ({@code String})
- * - Parameter 5: user-friendly variant of digest algorithm ({@code String})
- * - Parameter 6: user-friendly variant of signature algorithm ({@code String})
- *
- */
- JAR_SIG_UNSUPPORTED_SIG_ALG(
- "JAR signature %1$s uses digest algorithm %5$s and signature algorithm %6$s which"
- + " is not supported on API Level(s) %4$s for which this APK is being"
- + " verified"),
-
- /**
- * An exception was encountered while parsing JAR signature contained in a signature block.
- *
- *
- * - Parameter 1: name of the signature block file ({@code String})
- * - Parameter 2: exception ({@code Throwable})
- *
- */
- JAR_SIG_PARSE_EXCEPTION("Failed to parse JAR signature %1$s: %2$s"),
-
- /**
- * An exception was encountered while parsing a certificate contained in the JAR signature
- * block.
- *
- *
- * - Parameter 1: name of the signature block file ({@code String})
- * - Parameter 2: exception ({@code Throwable})
- *
- */
- JAR_SIG_MALFORMED_CERTIFICATE("Malformed certificate in JAR signature %1$s: %2$s"),
-
- /**
- * JAR signature contained in a signature block file did not verify against the signature
- * file.
- *
- *
- * - Parameter 1: name of the signature block file ({@code String})
- * - Parameter 2: name of the signature file ({@code String})
- *
- */
- JAR_SIG_DID_NOT_VERIFY("JAR signature %1$s did not verify against %2$s"),
-
- /**
- * JAR signature contains no verified signers.
- *
- *
- * - Parameter 1: name of the signature block file ({@code String})
- *
- */
- JAR_SIG_NO_SIGNERS("JAR signature %1$s contains no signers"),
-
- /**
- * JAR signature file contains a section with a duplicate name.
- *
- *
- * - Parameter 1: signature file name ({@code String})
- * - Parameter 1: section name ({@code String})
- *
- */
- JAR_SIG_DUPLICATE_SIG_FILE_SECTION("Duplicate section in %1$s: %2$s"),
-
- /**
- * JAR signature file's main section doesn't contain the mandatory Signature-Version
- * attribute.
- *
- *
- * - Parameter 1: signature file name ({@code String})
- *
- */
- JAR_SIG_MISSING_VERSION_ATTR_IN_SIG_FILE(
- "Malformed %1$s: missing Signature-Version attribute"),
-
- /**
- * JAR signature file references an unknown APK signature scheme ID.
- *
- *
- * - Parameter 1: name of the signature file ({@code String})
- * - Parameter 2: unknown APK signature scheme ID ({@code} Integer)
- *
- */
- JAR_SIG_UNKNOWN_APK_SIG_SCHEME_ID(
- "JAR signature %1$s references unknown APK signature scheme ID: %2$d"),
-
- /**
- * JAR signature file indicates that the APK is supposed to be signed with a supported APK
- * signature scheme (in addition to the JAR signature) but no such signature was found in
- * the APK.
- *
- *
- * - Parameter 1: name of the signature file ({@code String})
- * - Parameter 2: APK signature scheme ID ({@code} Integer)
- * - Parameter 3: APK signature scheme English name ({@code} String)
- *
- */
- JAR_SIG_MISSING_APK_SIG_REFERENCED(
- "JAR signature %1$s indicates the APK is signed using %3$s but no such signature"
- + " was found. Signature stripped?"),
-
- /**
- * JAR entry is not covered by signature and thus unauthorized modifications to its contents
- * will not be detected.
- *
- *
- * - Parameter 1: entry name ({@code String})
- *
- */
- JAR_SIG_UNPROTECTED_ZIP_ENTRY(
- "%1$s not protected by signature. Unauthorized modifications to this JAR entry"
- + " will not be detected. Delete or move the entry outside of META-INF/."),
-
- /**
- * APK which is both JAR-signed and signed using APK Signature Scheme v2 contains an APK
- * Signature Scheme v2 signature from this signer, but does not contain a JAR signature
- * from this signer.
- */
- JAR_SIG_MISSING("No JAR signature from this signer"),
-
- /**
- * APK is targeting a sandbox version which requires APK Signature Scheme v2 signature but
- * no such signature was found.
- *
- *
- * - Parameter 1: target sandbox version ({@code Integer})
- *
- */
- NO_SIG_FOR_TARGET_SANDBOX_VERSION(
- "Missing APK Signature Scheme v2 signature required for target sandbox version"
- + " %1$d"),
-
- /**
- * APK is targeting an SDK version that requires a minimum signature scheme version, but the
- * APK is not signed with that version or later.
- *
- *
- * - Parameter 1: target SDK Version (@code Integer})
- * - Parameter 2: minimum signature scheme version ((@code Integer})
- *
- */
- MIN_SIG_SCHEME_FOR_TARGET_SDK_NOT_MET(
- "Target SDK version %1$d requires a minimum of signature scheme v%2$d; the APK is"
- + " not signed with this or a later signature scheme"),
-
- /**
- * APK which is both JAR-signed and signed using APK Signature Scheme v2 contains a JAR
- * signature from this signer, but does not contain an APK Signature Scheme v2 signature
- * from this signer.
- */
- V2_SIG_MISSING("No APK Signature Scheme v2 signature from this signer"),
-
- /**
- * Failed to parse the list of signers contained in the APK Signature Scheme v2 signature.
- */
- V2_SIG_MALFORMED_SIGNERS("Malformed list of signers"),
-
- /**
- * Failed to parse this signer's signer block contained in the APK Signature Scheme v2
- * signature.
- */
- V2_SIG_MALFORMED_SIGNER("Malformed signer block"),
-
- /**
- * Public key embedded in the APK Signature Scheme v2 signature of this signer could not be
- * parsed.
- *
- *
- * - Parameter 1: error details ({@code Throwable})
- *
- */
- V2_SIG_MALFORMED_PUBLIC_KEY("Malformed public key: %1$s"),
-
- /**
- * This APK Signature Scheme v2 signer's certificate could not be parsed.
- *
- *
- * - Parameter 1: index ({@code 0}-based) of the certificate in the signer's list of
- * certificates ({@code Integer})
- * - Parameter 2: sequence number ({@code 1}-based) of the certificate in the signer's
- * list of certificates ({@code Integer})
- * - Parameter 3: error details ({@code Throwable})
- *
- */
- V2_SIG_MALFORMED_CERTIFICATE("Malformed certificate #%2$d: %3$s"),
-
- /**
- * Failed to parse this signer's signature record contained in the APK Signature Scheme v2
- * signature.
- *
- *
- * - Parameter 1: record number (first record is {@code 1}) ({@code Integer})
- *
- */
- V2_SIG_MALFORMED_SIGNATURE("Malformed APK Signature Scheme v2 signature record #%1$d"),
-
- /**
- * Failed to parse this signer's digest record contained in the APK Signature Scheme v2
- * signature.
- *
- *
- * - Parameter 1: record number (first record is {@code 1}) ({@code Integer})
- *
- */
- V2_SIG_MALFORMED_DIGEST("Malformed APK Signature Scheme v2 digest record #%1$d"),
-
- /**
- * This APK Signature Scheme v2 signer contains a malformed additional attribute.
- *
- *
- * - Parameter 1: attribute number (first attribute is {@code 1}) {@code Integer})
- *
- */
- V2_SIG_MALFORMED_ADDITIONAL_ATTRIBUTE("Malformed additional attribute #%1$d"),
-
- /**
- * APK Signature Scheme v2 signature references an unknown APK signature scheme ID.
- *
- *
- * - Parameter 1: signer index ({@code Integer})
- * - Parameter 2: unknown APK signature scheme ID ({@code} Integer)
- *
- */
- V2_SIG_UNKNOWN_APK_SIG_SCHEME_ID(
- "APK Signature Scheme v2 signer: %1$s references unknown APK signature scheme ID: "
- + "%2$d"),
-
- /**
- * APK Signature Scheme v2 signature indicates that the APK is supposed to be signed with a
- * supported APK signature scheme (in addition to the v2 signature) but no such signature
- * was found in the APK.
- *
- *
- * - Parameter 1: signer index ({@code Integer})
- * - Parameter 2: APK signature scheme English name ({@code} String)
- *
- */
- V2_SIG_MISSING_APK_SIG_REFERENCED(
- "APK Signature Scheme v2 signature %1$s indicates the APK is signed using %2$s but "
- + "no such signature was found. Signature stripped?"),
-
- /**
- * APK Signature Scheme v2 signature contains no signers.
- */
- V2_SIG_NO_SIGNERS("No signers in APK Signature Scheme v2 signature"),
-
- /**
- * This APK Signature Scheme v2 signer contains a signature produced using an unknown
- * algorithm.
- *
- *
- * - Parameter 1: algorithm ID ({@code Integer})
- *
- */
- V2_SIG_UNKNOWN_SIG_ALGORITHM("Unknown signature algorithm: %1$#x"),
-
- /**
- * This APK Signature Scheme v2 signer contains an unknown additional attribute.
- *
- *
- * - Parameter 1: attribute ID ({@code Integer})
- *
- */
- V2_SIG_UNKNOWN_ADDITIONAL_ATTRIBUTE("Unknown additional attribute: ID %1$#x"),
-
- /**
- * An exception was encountered while verifying APK Signature Scheme v2 signature of this
- * signer.
- *
- *
- * - Parameter 1: signature algorithm ({@link SignatureAlgorithm})
- * - Parameter 2: exception ({@code Throwable})
- *
- */
- V2_SIG_VERIFY_EXCEPTION("Failed to verify %1$s signature: %2$s"),
-
- /**
- * APK Signature Scheme v2 signature over this signer's signed-data block did not verify.
- *
- *
- * - Parameter 1: signature algorithm ({@link SignatureAlgorithm})
- *
- */
- V2_SIG_DID_NOT_VERIFY("%1$s signature over signed-data did not verify"),
-
- /**
- * This APK Signature Scheme v2 signer offers no signatures.
- */
- V2_SIG_NO_SIGNATURES("No signatures"),
-
- /**
- * This APK Signature Scheme v2 signer offers signatures but none of them are supported.
- */
- V2_SIG_NO_SUPPORTED_SIGNATURES("No supported signatures: %1$s"),
-
- /**
- * This APK Signature Scheme v2 signer offers no certificates.
- */
- V2_SIG_NO_CERTIFICATES("No certificates"),
-
- /**
- * This APK Signature Scheme v2 signer's public key listed in the signer's certificate does
- * not match the public key listed in the signatures record.
- *
- *
- * - Parameter 1: hex-encoded public key from certificate ({@code String})
- * - Parameter 2: hex-encoded public key from signatures record ({@code String})
- *
- */
- V2_SIG_PUBLIC_KEY_MISMATCH_BETWEEN_CERTIFICATE_AND_SIGNATURES_RECORD(
- "Public key mismatch between certificate and signature record: <%1$s> vs <%2$s>"),
-
- /**
- * This APK Signature Scheme v2 signer's signature algorithms listed in the signatures
- * record do not match the signature algorithms listed in the signatures record.
- *
- *
- * - Parameter 1: signature algorithms from signatures record ({@code List})
- * - Parameter 2: signature algorithms from digests record ({@code List})
- *
- */
- V2_SIG_SIG_ALG_MISMATCH_BETWEEN_SIGNATURES_AND_DIGESTS_RECORDS(
- "Signature algorithms mismatch between signatures and digests records"
- + ": %1$s vs %2$s"),
-
- /**
- * The APK's digest does not match the digest contained in the APK Signature Scheme v2
- * signature.
- *
- *
- * - Parameter 1: content digest algorithm ({@link ContentDigestAlgorithm})
- * - Parameter 2: hex-encoded expected digest of the APK ({@code String})
- * - Parameter 3: hex-encoded actual digest of the APK ({@code String})
- *
- */
- V2_SIG_APK_DIGEST_DID_NOT_VERIFY(
- "APK integrity check failed. %1$s digest mismatch."
- + " Expected: <%2$s>, actual: <%3$s>"),
-
- /**
- * Failed to parse the list of signers contained in the APK Signature Scheme v3 signature.
- */
- V3_SIG_MALFORMED_SIGNERS("Malformed list of signers"),
-
- /**
- * Failed to parse this signer's signer block contained in the APK Signature Scheme v3
- * signature.
- */
- V3_SIG_MALFORMED_SIGNER("Malformed signer block"),
-
- /**
- * Public key embedded in the APK Signature Scheme v3 signature of this signer could not be
- * parsed.
- *
- *
- * - Parameter 1: error details ({@code Throwable})
- *
- */
- V3_SIG_MALFORMED_PUBLIC_KEY("Malformed public key: %1$s"),
-
- /**
- * This APK Signature Scheme v3 signer's certificate could not be parsed.
- *
- *
- * - Parameter 1: index ({@code 0}-based) of the certificate in the signer's list of
- * certificates ({@code Integer})
- * - Parameter 2: sequence number ({@code 1}-based) of the certificate in the signer's
- * list of certificates ({@code Integer})
- * - Parameter 3: error details ({@code Throwable})
- *
- */
- V3_SIG_MALFORMED_CERTIFICATE("Malformed certificate #%2$d: %3$s"),
-
- /**
- * Failed to parse this signer's signature record contained in the APK Signature Scheme v3
- * signature.
- *
- *
- * - Parameter 1: record number (first record is {@code 1}) ({@code Integer})
- *
- */
- V3_SIG_MALFORMED_SIGNATURE("Malformed APK Signature Scheme v3 signature record #%1$d"),
-
- /**
- * Failed to parse this signer's digest record contained in the APK Signature Scheme v3
- * signature.
- *
- *
- * - Parameter 1: record number (first record is {@code 1}) ({@code Integer})
- *
- */
- V3_SIG_MALFORMED_DIGEST("Malformed APK Signature Scheme v3 digest record #%1$d"),
-
- /**
- * This APK Signature Scheme v3 signer contains a malformed additional attribute.
- *
- *
- * - Parameter 1: attribute number (first attribute is {@code 1}) {@code Integer})
- *
- */
- V3_SIG_MALFORMED_ADDITIONAL_ATTRIBUTE("Malformed additional attribute #%1$d"),
-
- /**
- * APK Signature Scheme v3 signature contains no signers.
- */
- V3_SIG_NO_SIGNERS("No signers in APK Signature Scheme v3 signature"),
-
- /**
- * APK Signature Scheme v3 signature contains multiple signers (only one allowed per
- * platform version).
- */
- V3_SIG_MULTIPLE_SIGNERS("Multiple APK Signature Scheme v3 signatures found for a single "
- + " platform version."),
-
- /**
- * APK Signature Scheme v3 signature found, but multiple v1 and/or multiple v2 signers
- * found, where only one may be used with APK Signature Scheme v3
- */
- V3_SIG_MULTIPLE_PAST_SIGNERS("Multiple signatures found for pre-v3 signing with an APK "
- + " Signature Scheme v3 signer. Only one allowed."),
-
- /**
- * APK Signature Scheme v3 signature found, but its signer doesn't match the v1/v2 signers,
- * or have them as the root of its signing certificate history
- */
- V3_SIG_PAST_SIGNERS_MISMATCH(
- "v3 signer differs from v1/v2 signer without proper signing certificate lineage."),
-
- /**
- * This APK Signature Scheme v3 signer contains a signature produced using an unknown
- * algorithm.
- *
- *
- * - Parameter 1: algorithm ID ({@code Integer})
- *
- */
- V3_SIG_UNKNOWN_SIG_ALGORITHM("Unknown signature algorithm: %1$#x"),
-
- /**
- * This APK Signature Scheme v3 signer contains an unknown additional attribute.
- *
- *
- * - Parameter 1: attribute ID ({@code Integer})
- *
- */
- V3_SIG_UNKNOWN_ADDITIONAL_ATTRIBUTE("Unknown additional attribute: ID %1$#x"),
-
- /**
- * An exception was encountered while verifying APK Signature Scheme v3 signature of this
- * signer.
- *
- *
- * - Parameter 1: signature algorithm ({@link SignatureAlgorithm})
- * - Parameter 2: exception ({@code Throwable})
- *
- */
- V3_SIG_VERIFY_EXCEPTION("Failed to verify %1$s signature: %2$s"),
-
- /**
- * The APK Signature Scheme v3 signer contained an invalid value for either min or max SDK
- * versions.
- *
- *
- * - Parameter 1: minSdkVersion ({@code Integer})
- *
- Parameter 2: maxSdkVersion ({@code Integer})
- *
- */
- V3_SIG_INVALID_SDK_VERSIONS("Invalid SDK Version parameter(s) encountered in APK Signature "
- + "scheme v3 signature: minSdkVersion %1$s maxSdkVersion: %2$s"),
-
- /**
- * APK Signature Scheme v3 signature over this signer's signed-data block did not verify.
- *
- *
- * - Parameter 1: signature algorithm ({@link SignatureAlgorithm})
- *
- */
- V3_SIG_DID_NOT_VERIFY("%1$s signature over signed-data did not verify"),
-
- /**
- * This APK Signature Scheme v3 signer offers no signatures.
- */
- V3_SIG_NO_SIGNATURES("No signatures"),
-
- /**
- * This APK Signature Scheme v3 signer offers signatures but none of them are supported.
- */
- V3_SIG_NO_SUPPORTED_SIGNATURES("No supported signatures"),
-
- /**
- * This APK Signature Scheme v3 signer offers no certificates.
- */
- V3_SIG_NO_CERTIFICATES("No certificates"),
-
- /**
- * This APK Signature Scheme v3 signer's minSdkVersion listed in the signer's signed data
- * does not match the minSdkVersion listed in the signatures record.
- *
- *
- * - Parameter 1: minSdkVersion in signature record ({@code Integer})
- * - Parameter 2: minSdkVersion in signed data ({@code Integer})
- *
- */
- V3_MIN_SDK_VERSION_MISMATCH_BETWEEN_SIGNER_AND_SIGNED_DATA_RECORD(
- "minSdkVersion mismatch between signed data and signature record:"
- + " <%1$s> vs <%2$s>"),
-
- /**
- * This APK Signature Scheme v3 signer's maxSdkVersion listed in the signer's signed data
- * does not match the maxSdkVersion listed in the signatures record.
- *
- *
- * - Parameter 1: maxSdkVersion in signature record ({@code Integer})
- * - Parameter 2: maxSdkVersion in signed data ({@code Integer})
- *
- */
- V3_MAX_SDK_VERSION_MISMATCH_BETWEEN_SIGNER_AND_SIGNED_DATA_RECORD(
- "maxSdkVersion mismatch between signed data and signature record:"
- + " <%1$s> vs <%2$s>"),
-
- /**
- * This APK Signature Scheme v3 signer's public key listed in the signer's certificate does
- * not match the public key listed in the signatures record.
- *
- *
- * - Parameter 1: hex-encoded public key from certificate ({@code String})
- * - Parameter 2: hex-encoded public key from signatures record ({@code String})
- *
- */
- V3_SIG_PUBLIC_KEY_MISMATCH_BETWEEN_CERTIFICATE_AND_SIGNATURES_RECORD(
- "Public key mismatch between certificate and signature record: <%1$s> vs <%2$s>"),
-
- /**
- * This APK Signature Scheme v3 signer's signature algorithms listed in the signatures
- * record do not match the signature algorithms listed in the signatures record.
- *
- *
- * - Parameter 1: signature algorithms from signatures record ({@code List})
- * - Parameter 2: signature algorithms from digests record ({@code List})
- *
- */
- V3_SIG_SIG_ALG_MISMATCH_BETWEEN_SIGNATURES_AND_DIGESTS_RECORDS(
- "Signature algorithms mismatch between signatures and digests records"
- + ": %1$s vs %2$s"),
-
- /**
- * The APK's digest does not match the digest contained in the APK Signature Scheme v3
- * signature.
- *
- *
- * - Parameter 1: content digest algorithm ({@link ContentDigestAlgorithm})
- * - Parameter 2: hex-encoded expected digest of the APK ({@code String})
- * - Parameter 3: hex-encoded actual digest of the APK ({@code String})
- *
- */
- V3_SIG_APK_DIGEST_DID_NOT_VERIFY(
- "APK integrity check failed. %1$s digest mismatch."
- + " Expected: <%2$s>, actual: <%3$s>"),
-
- /**
- * The signer's SigningCertificateLineage attribute containd a proof-of-rotation record with
- * signature(s) that did not verify.
- */
- V3_SIG_POR_DID_NOT_VERIFY("SigningCertificateLineage attribute containd a proof-of-rotation"
- + " record with signature(s) that did not verify."),
-
- /**
- * Failed to parse the SigningCertificateLineage structure in the APK Signature Scheme v3
- * signature's additional attributes section.
- */
- V3_SIG_MALFORMED_LINEAGE("Failed to parse the SigningCertificateLineage structure in the "
- + "APK Signature Scheme v3 signature's additional attributes section."),
-
- /**
- * The APK's signing certificate does not match the terminal node in the provided
- * proof-of-rotation structure describing the signing certificate history
- */
- V3_SIG_POR_CERT_MISMATCH(
- "APK signing certificate differs from the associated certificate found in the "
- + "signer's SigningCertificateLineage."),
-
- /**
- * The APK Signature Scheme v3 signers encountered do not offer a continuous set of
- * supported platform versions. Either they overlap, resulting in potentially two
- * acceptable signers for a platform version, or there are holes which would create problems
- * in the event of platform version upgrades.
- */
- V3_INCONSISTENT_SDK_VERSIONS("APK Signature Scheme v3 signers supported min/max SDK "
- + "versions are not continuous."),
-
- /**
- * The APK Signature Scheme v3 signers don't cover all requested SDK versions.
- *
- *
- * - Parameter 1: minSdkVersion ({@code Integer})
- *
- Parameter 2: maxSdkVersion ({@code Integer})
- *
- */
- V3_MISSING_SDK_VERSIONS("APK Signature Scheme v3 signers supported min/max SDK "
- + "versions do not cover the entire desired range. Found min: %1$s max %2$s"),
-
- /**
- * The SigningCertificateLineages for different platform versions using APK Signature Scheme
- * v3 do not go together. Specifically, each should be a subset of another, with the size
- * of each increasing as the platform level increases.
- */
- V3_INCONSISTENT_LINEAGES("SigningCertificateLineages targeting different platform versions"
- + " using APK Signature Scheme v3 are not all a part of the same overall lineage."),
-
- /**
- * APK Signing Block contains an unknown entry.
- *
- *
- * - Parameter 1: entry ID ({@code Integer})
- *
- */
- APK_SIG_BLOCK_UNKNOWN_ENTRY_ID("APK Signing Block contains unknown entry: ID %1$#x"),
-
- /**
- * Failed to parse this signer's signature record contained in the APK Signature Scheme
- * V4 signature.
- *
- *
- * - Parameter 1: record number (first record is {@code 1}) ({@code Integer})
- *
- */
- V4_SIG_MALFORMED_SIGNERS(
- "V4 signature has malformed signer block"),
-
- /**
- * This APK Signature Scheme V4 signer contains a signature produced using an
- * unknown algorithm.
- *
- *
- * - Parameter 1: algorithm ID ({@code Integer})
- *
- */
- V4_SIG_UNKNOWN_SIG_ALGORITHM(
- "V4 signature has unknown signing algorithm: %1$#x"),
-
- /**
- * This APK Signature Scheme V4 signer offers no signatures.
- */
- V4_SIG_NO_SIGNATURES(
- "V4 signature has no signature found"),
-
- /**
- * This APK Signature Scheme V4 signer offers signatures but none of them are
- * supported.
- */
- V4_SIG_NO_SUPPORTED_SIGNATURES(
- "V4 signature has no supported signature"),
-
- /**
- * APK Signature Scheme v3 signature over this signer's signed-data block did not verify.
- *
- *
- * - Parameter 1: signature algorithm ({@link SignatureAlgorithm})
- *
- */
- V4_SIG_DID_NOT_VERIFY("%1$s signature over signed-data did not verify"),
-
- /**
- * An exception was encountered while verifying APK Signature Scheme v3 signature of this
- * signer.
- *
- *
- * - Parameter 1: signature algorithm ({@link SignatureAlgorithm})
- * - Parameter 2: exception ({@code Throwable})
- *
- */
- V4_SIG_VERIFY_EXCEPTION("Failed to verify %1$s signature: %2$s"),
-
- /**
- * Public key embedded in the APK Signature Scheme v4 signature of this signer could not be
- * parsed.
- *
- *
- * - Parameter 1: error details ({@code Throwable})
- *
- */
- V4_SIG_MALFORMED_PUBLIC_KEY("Malformed public key: %1$s"),
-
- /**
- * This APK Signature Scheme V4 signer's certificate could not be parsed.
- *
- *
- * - Parameter 1: index ({@code 0}-based) of the certificate in the signer's list of
- * certificates ({@code Integer})
- * - Parameter 2: sequence number ({@code 1}-based) of the certificate in the signer's
- * list of certificates ({@code Integer})
- * - Parameter 3: error details ({@code Throwable})
- *
- */
- V4_SIG_MALFORMED_CERTIFICATE(
- "V4 signature has malformed certificate"),
-
- /**
- * This APK Signature Scheme V4 signer offers no certificate.
- */
- V4_SIG_NO_CERTIFICATE("V4 signature has no certificate"),
-
- /**
- * This APK Signature Scheme V4 signer's public key listed in the signer's
- * certificate does not match the public key listed in the signature proto.
- *
- *
- * - Parameter 1: hex-encoded public key from certificate ({@code String})
- * - Parameter 2: hex-encoded public key from signature proto ({@code String})
- *
- */
- V4_SIG_PUBLIC_KEY_MISMATCH_BETWEEN_CERTIFICATE_AND_SIGNATURES_RECORD(
- "V4 signature has mismatched certificate and signature: <%1$s> vs <%2$s>"),
-
- /**
- * The APK's hash root (aka digest) does not match the hash root contained in the Signature
- * Scheme V4 signature.
- *
- *
- * - Parameter 1: content digest algorithm ({@link ContentDigestAlgorithm})
- * - Parameter 2: hex-encoded expected digest of the APK ({@code String})
- * - Parameter 3: hex-encoded actual digest of the APK ({@code String})
- *
- */
- V4_SIG_APK_ROOT_DID_NOT_VERIFY(
- "V4 signature's hash tree root (content digest) did not verity"),
-
- /**
- * The APK's hash tree does not match the hash tree contained in the Signature
- * Scheme V4 signature.
- *
- *
- * - Parameter 1: content digest algorithm ({@link ContentDigestAlgorithm})
- * - Parameter 2: hex-encoded expected hash tree of the APK ({@code String})
- * - Parameter 3: hex-encoded actual hash tree of the APK ({@code String})
- *
- */
- V4_SIG_APK_TREE_DID_NOT_VERIFY(
- "V4 signature's hash tree did not verity"),
-
- /**
- * Using more than one Signer to sign APK Signature Scheme V4 signature.
- */
- V4_SIG_MULTIPLE_SIGNERS(
- "V4 signature only supports one signer"),
-
- /**
- * The signer used to sign APK Signature Scheme V2/V3 signature does not match the signer
- * used to sign APK Signature Scheme V4 signature.
- */
- V4_SIG_V2_V3_SIGNERS_MISMATCH(
- "V4 signature and V2/V3 signature have mismatched certificates"),
-
- V4_SIG_V2_V3_DIGESTS_MISMATCH(
- "V4 signature and V2/V3 signature have mismatched digests"),
-
- /**
- * The v4 signature format version isn't the same as the tool's current version, something
- * may go wrong.
- */
- V4_SIG_VERSION_NOT_CURRENT(
- "V4 signature format version %1$d is different from the tool's current "
- + "version %2$d"),
-
- /**
- * The APK does not contain the source stamp certificate digest file nor the signature block
- * when verification expected a source stamp to be present.
- */
- SOURCE_STAMP_CERT_DIGEST_AND_SIG_BLOCK_MISSING(
- "Neither the source stamp certificate digest file nor the signature block are "
- + "present in the APK"),
-
- /** APK contains SourceStamp file, but does not contain a SourceStamp signature. */
- SOURCE_STAMP_SIG_MISSING("No SourceStamp signature"),
-
- /**
- * SourceStamp's certificate could not be parsed.
- *
- *
- * - Parameter 1: error details ({@code Throwable})
- *
- */
- SOURCE_STAMP_MALFORMED_CERTIFICATE("Malformed certificate: %1$s"),
-
- /** Failed to parse SourceStamp's signature. */
- SOURCE_STAMP_MALFORMED_SIGNATURE("Malformed SourceStamp signature"),
-
- /**
- * SourceStamp contains a signature produced using an unknown algorithm.
- *
- *
- * - Parameter 1: algorithm ID ({@code Integer})
- *
- */
- SOURCE_STAMP_UNKNOWN_SIG_ALGORITHM("Unknown signature algorithm: %1$#x"),
-
- /**
- * An exception was encountered while verifying SourceStamp signature.
- *
- *
- * - Parameter 1: signature algorithm ({@link SignatureAlgorithm})
- *
- Parameter 2: exception ({@code Throwable})
- *
- */
- SOURCE_STAMP_VERIFY_EXCEPTION("Failed to verify %1$s signature: %2$s"),
-
- /**
- * SourceStamp signature block did not verify.
- *
- *
- * - Parameter 1: signature algorithm ({@link SignatureAlgorithm})
- *
- */
- SOURCE_STAMP_DID_NOT_VERIFY("%1$s signature over signed-data did not verify"),
-
- /** SourceStamp offers no signatures. */
- SOURCE_STAMP_NO_SIGNATURE("No signature"),
-
- /**
- * SourceStamp offers an unsupported signature.
- *
- * - Parameter 1: list of {@link SignatureAlgorithm}s in the source stamp
- * signing block.
- *
- Parameter 2: {@code Exception} caught when attempting to obtain the list of
- * supported signatures.
- *
- */
- SOURCE_STAMP_NO_SUPPORTED_SIGNATURE("Signature(s) {%1$s} not supported: %2$s"),
-
- /**
- * SourceStamp's certificate listed in the APK signing block does not match the certificate
- * listed in the SourceStamp file in the APK.
- *
- *
- * - Parameter 1: SHA-256 hash of certificate from SourceStamp block in APK signing
- * block ({@code String})
- *
- Parameter 2: SHA-256 hash of certificate from SourceStamp file in APK ({@code
- * String})
- *
- */
- SOURCE_STAMP_CERTIFICATE_MISMATCH_BETWEEN_SIGNATURE_BLOCK_AND_APK(
- "Certificate mismatch between SourceStamp block in APK signing block and"
- + " SourceStamp file in APK: <%1$s> vs <%2$s>"),
-
- /**
- * The APK contains a source stamp signature block without the expected certificate digest
- * in the APK contents.
- */
- SOURCE_STAMP_SIGNATURE_BLOCK_WITHOUT_CERT_DIGEST(
- "A source stamp signature block was found without a corresponding certificate "
- + "digest in the APK"),
-
- /**
- * When verifying just the source stamp, the certificate digest in the APK does not match
- * the expected digest.
- *
- * - Parameter 1: SHA-256 digest of the source stamp certificate in the APK.
- *
- Parameter 2: SHA-256 digest of the expected source stamp certificate.
- *
- */
- SOURCE_STAMP_EXPECTED_DIGEST_MISMATCH(
- "The source stamp certificate digest in the APK, %1$s, does not match the "
- + "expected digest, %2$s"),
-
- /**
- * Source stamp block contains a malformed attribute.
- *
- *
- * - Parameter 1: attribute number (first attribute is {@code 1}) {@code Integer})
- *
- */
- SOURCE_STAMP_MALFORMED_ATTRIBUTE("Malformed stamp attribute #%1$d"),
-
- /**
- * Source stamp block contains an unknown attribute.
- *
- *
- * - Parameter 1: attribute ID ({@code Integer})
- *
- */
- SOURCE_STAMP_UNKNOWN_ATTRIBUTE("Unknown stamp attribute: ID %1$#x"),
-
- /**
- * Failed to parse the SigningCertificateLineage structure in the source stamp
- * attributes section.
- */
- SOURCE_STAMP_MALFORMED_LINEAGE("Failed to parse the SigningCertificateLineage "
- + "structure in the source stamp attributes section."),
-
- /**
- * The source stamp certificate does not match the terminal node in the provided
- * proof-of-rotation structure describing the stamp certificate history.
- */
- SOURCE_STAMP_POR_CERT_MISMATCH(
- "APK signing certificate differs from the associated certificate found in the "
- + "signer's SigningCertificateLineage."),
-
- /**
- * The source stamp SigningCertificateLineage attribute contains a proof-of-rotation record
- * with signature(s) that did not verify.
- */
- SOURCE_STAMP_POR_DID_NOT_VERIFY("Source stamp SigningCertificateLineage attribute "
- + "contains a proof-of-rotation record with signature(s) that did not verify."),
-
- /**
- * The APK could not be properly parsed due to a ZIP or APK format exception.
- *
- * - Parameter 1: The {@code Exception} caught when attempting to parse the APK.
- *
- */
- MALFORMED_APK(
- "Malformed APK; the following exception was caught when attempting to parse the "
- + "APK: %1$s"),
-
- /**
- * An unexpected exception was caught when attempting to verify the signature(s) within the
- * APK.
- *
- * - Parameter 1: The {@code Exception} caught during verification.
- *
- */
- UNEXPECTED_EXCEPTION(
- "An unexpected exception was caught when verifying the signature: %1$s");
-
- private final String mFormat;
-
- Issue(String format) {
- mFormat = format;
- }
-
- /**
- * Returns the format string suitable for combining the parameters of this issue into a
- * readable string. See {@link java.util.Formatter} for format.
- */
- private String getFormat() {
- return mFormat;
- }
- }
-
- /**
- * {@link Issue} with associated parameters. {@link #toString()} produces a readable formatted
- * form.
- */
- public static class IssueWithParams extends ApkVerificationIssue {
- private final Issue mIssue;
- private final Object[] mParams;
-
- /**
- * Constructs a new {@code IssueWithParams} of the specified type and with provided
- * parameters.
- */
- public IssueWithParams(Issue issue, Object[] params) {
- super(issue.mFormat, params);
- mIssue = issue;
- mParams = params;
- }
-
- /**
- * Returns the type of this issue.
- */
- public Issue getIssue() {
- return mIssue;
- }
-
- /**
- * Returns the parameters of this issue.
- */
- public Object[] getParams() {
- return mParams.clone();
- }
-
- /**
- * Returns a readable form of this issue.
- */
- @Override
- public String toString() {
- return String.format(mIssue.getFormat(), mParams);
- }
- }
-
- /**
- * Wrapped around {@code byte[]} which ensures that {@code equals} and {@code hashCode} operate
- * on the contents of the arrays rather than on references.
- */
- private static class ByteArray {
- private final byte[] mArray;
- private final int mHashCode;
-
- private ByteArray(byte[] arr) {
- mArray = arr;
- mHashCode = Arrays.hashCode(mArray);
- }
-
- @Override
- public int hashCode() {
- return mHashCode;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (!(obj instanceof ByteArray)) {
- return false;
- }
- ByteArray other = (ByteArray) obj;
- if (hashCode() != other.hashCode()) {
- return false;
- }
- if (!Arrays.equals(mArray, other.mArray)) {
- return false;
- }
- return true;
- }
- }
-
- /**
- * Builder of {@link ApkVerifier} instances.
- *
- * The resulting verifier by default checks whether the APK will verify on all platform
- * versions supported by the APK, as specified by {@code android:minSdkVersion} attributes in
- * the APK's {@code AndroidManifest.xml}. The range of platform versions can be customized using
- * {@link #setMinCheckedPlatformVersion(int)} and {@link #setMaxCheckedPlatformVersion(int)}.
- */
- public static class Builder {
- private final File mApkFile;
- private final DataSource mApkDataSource;
- private File mV4SignatureFile;
-
- private Integer mMinSdkVersion;
- private int mMaxSdkVersion = Integer.MAX_VALUE;
-
- /**
- * Constructs a new {@code Builder} for verifying the provided APK file.
- */
- public Builder(File apk) {
- if (apk == null) {
- throw new NullPointerException("apk == null");
- }
- mApkFile = apk;
- mApkDataSource = null;
- }
-
- /**
- * Constructs a new {@code Builder} for verifying the provided APK.
- */
- public Builder(DataSource apk) {
- if (apk == null) {
- throw new NullPointerException("apk == null");
- }
- mApkDataSource = apk;
- mApkFile = null;
- }
-
- /**
- * Sets the oldest Android platform version for which the APK is verified. APK verification
- * will confirm that the APK is expected to install successfully on all known Android
- * platforms starting from the platform version with the provided API Level. The upper end
- * of the platform versions range can be modified via
- * {@link #setMaxCheckedPlatformVersion(int)}.
- *
- *
This method is useful for overriding the default behavior which checks that the APK
- * will verify on all platform versions supported by the APK, as specified by
- * {@code android:minSdkVersion} attributes in the APK's {@code AndroidManifest.xml}.
- *
- * @param minSdkVersion API Level of the oldest platform for which to verify the APK
- * @see #setMinCheckedPlatformVersion(int)
- */
- public Builder setMinCheckedPlatformVersion(int minSdkVersion) {
- mMinSdkVersion = minSdkVersion;
- return this;
- }
-
- /**
- * Sets the newest Android platform version for which the APK is verified. APK verification
- * will confirm that the APK is expected to install successfully on all platform versions
- * supported by the APK up until and including the provided version. The lower end
- * of the platform versions range can be modified via
- * {@link #setMinCheckedPlatformVersion(int)}.
- *
- * @param maxSdkVersion API Level of the newest platform for which to verify the APK
- * @see #setMinCheckedPlatformVersion(int)
- */
- public Builder setMaxCheckedPlatformVersion(int maxSdkVersion) {
- mMaxSdkVersion = maxSdkVersion;
- return this;
- }
-
- public Builder setV4SignatureFile(File v4SignatureFile) {
- mV4SignatureFile = v4SignatureFile;
- return this;
- }
-
- /**
- * Returns an {@link ApkVerifier} initialized according to the configuration of this
- * builder.
- */
- public ApkVerifier build() {
- return new ApkVerifier(
- mApkFile,
- mApkDataSource,
- mV4SignatureFile,
- mMinSdkVersion,
- mMaxSdkVersion);
- }
- }
-
- /**
- * Adapter for converting base {@link ApkVerificationIssue} instances to their {@link
- * IssueWithParams} equivalent.
- */
- public static class ApkVerificationIssueAdapter {
- private ApkVerificationIssueAdapter() {
- }
-
- // This field is visible for testing
- static final Map sVerificationIssueIdToIssue = new HashMap<>();
-
- static {
- sVerificationIssueIdToIssue.put(ApkVerificationIssue.V2_SIG_MALFORMED_SIGNERS,
- Issue.V2_SIG_MALFORMED_SIGNERS);
- sVerificationIssueIdToIssue.put(ApkVerificationIssue.V2_SIG_NO_SIGNERS,
- Issue.V2_SIG_NO_SIGNERS);
- sVerificationIssueIdToIssue.put(ApkVerificationIssue.V2_SIG_MALFORMED_SIGNER,
- Issue.V2_SIG_MALFORMED_SIGNER);
- sVerificationIssueIdToIssue.put(ApkVerificationIssue.V2_SIG_MALFORMED_SIGNATURE,
- Issue.V2_SIG_MALFORMED_SIGNATURE);
- sVerificationIssueIdToIssue.put(ApkVerificationIssue.V2_SIG_NO_SIGNATURES,
- Issue.V2_SIG_NO_SIGNATURES);
- sVerificationIssueIdToIssue.put(ApkVerificationIssue.V2_SIG_MALFORMED_CERTIFICATE,
- Issue.V2_SIG_MALFORMED_CERTIFICATE);
- sVerificationIssueIdToIssue.put(ApkVerificationIssue.V2_SIG_NO_CERTIFICATES,
- Issue.V2_SIG_NO_CERTIFICATES);
- sVerificationIssueIdToIssue.put(ApkVerificationIssue.V2_SIG_MALFORMED_DIGEST,
- Issue.V2_SIG_MALFORMED_DIGEST);
- sVerificationIssueIdToIssue.put(ApkVerificationIssue.V3_SIG_MALFORMED_SIGNERS,
- Issue.V3_SIG_MALFORMED_SIGNERS);
- sVerificationIssueIdToIssue.put(ApkVerificationIssue.V3_SIG_NO_SIGNERS,
- Issue.V3_SIG_NO_SIGNERS);
- sVerificationIssueIdToIssue.put(ApkVerificationIssue.V3_SIG_MALFORMED_SIGNER,
- Issue.V3_SIG_MALFORMED_SIGNER);
- sVerificationIssueIdToIssue.put(ApkVerificationIssue.V3_SIG_MALFORMED_SIGNATURE,
- Issue.V3_SIG_MALFORMED_SIGNATURE);
- sVerificationIssueIdToIssue.put(ApkVerificationIssue.V3_SIG_NO_SIGNATURES,
- Issue.V3_SIG_NO_SIGNATURES);
- sVerificationIssueIdToIssue.put(ApkVerificationIssue.V3_SIG_MALFORMED_CERTIFICATE,
- Issue.V3_SIG_MALFORMED_CERTIFICATE);
- sVerificationIssueIdToIssue.put(ApkVerificationIssue.V3_SIG_NO_CERTIFICATES,
- Issue.V3_SIG_NO_CERTIFICATES);
- sVerificationIssueIdToIssue.put(ApkVerificationIssue.V3_SIG_MALFORMED_DIGEST,
- Issue.V3_SIG_MALFORMED_DIGEST);
- sVerificationIssueIdToIssue.put(ApkVerificationIssue.SOURCE_STAMP_NO_SIGNATURE,
- Issue.SOURCE_STAMP_NO_SIGNATURE);
- sVerificationIssueIdToIssue.put(ApkVerificationIssue.SOURCE_STAMP_MALFORMED_CERTIFICATE,
- Issue.SOURCE_STAMP_MALFORMED_CERTIFICATE);
- sVerificationIssueIdToIssue.put(ApkVerificationIssue.SOURCE_STAMP_UNKNOWN_SIG_ALGORITHM,
- Issue.SOURCE_STAMP_UNKNOWN_SIG_ALGORITHM);
- sVerificationIssueIdToIssue.put(ApkVerificationIssue.SOURCE_STAMP_MALFORMED_SIGNATURE,
- Issue.SOURCE_STAMP_MALFORMED_SIGNATURE);
- sVerificationIssueIdToIssue.put(ApkVerificationIssue.SOURCE_STAMP_DID_NOT_VERIFY,
- Issue.SOURCE_STAMP_DID_NOT_VERIFY);
- sVerificationIssueIdToIssue.put(ApkVerificationIssue.SOURCE_STAMP_VERIFY_EXCEPTION,
- Issue.SOURCE_STAMP_VERIFY_EXCEPTION);
- sVerificationIssueIdToIssue.put(
- ApkVerificationIssue.SOURCE_STAMP_EXPECTED_DIGEST_MISMATCH,
- Issue.SOURCE_STAMP_EXPECTED_DIGEST_MISMATCH);
- sVerificationIssueIdToIssue.put(
- ApkVerificationIssue.SOURCE_STAMP_SIGNATURE_BLOCK_WITHOUT_CERT_DIGEST,
- Issue.SOURCE_STAMP_SIGNATURE_BLOCK_WITHOUT_CERT_DIGEST);
- sVerificationIssueIdToIssue.put(
- ApkVerificationIssue.SOURCE_STAMP_CERT_DIGEST_AND_SIG_BLOCK_MISSING,
- Issue.SOURCE_STAMP_CERT_DIGEST_AND_SIG_BLOCK_MISSING);
- sVerificationIssueIdToIssue.put(
- ApkVerificationIssue.SOURCE_STAMP_NO_SUPPORTED_SIGNATURE,
- Issue.SOURCE_STAMP_NO_SUPPORTED_SIGNATURE);
- sVerificationIssueIdToIssue.put(
- ApkVerificationIssue
- .SOURCE_STAMP_CERTIFICATE_MISMATCH_BETWEEN_SIGNATURE_BLOCK_AND_APK,
- Issue.SOURCE_STAMP_CERTIFICATE_MISMATCH_BETWEEN_SIGNATURE_BLOCK_AND_APK);
- sVerificationIssueIdToIssue.put(ApkVerificationIssue.MALFORMED_APK,
- Issue.MALFORMED_APK);
- sVerificationIssueIdToIssue.put(ApkVerificationIssue.UNEXPECTED_EXCEPTION,
- Issue.UNEXPECTED_EXCEPTION);
- sVerificationIssueIdToIssue.put(ApkVerificationIssue.SOURCE_STAMP_SIG_MISSING,
- Issue.SOURCE_STAMP_SIG_MISSING);
- sVerificationIssueIdToIssue.put(ApkVerificationIssue.SOURCE_STAMP_MALFORMED_ATTRIBUTE,
- Issue.SOURCE_STAMP_MALFORMED_ATTRIBUTE);
- sVerificationIssueIdToIssue.put(ApkVerificationIssue.SOURCE_STAMP_UNKNOWN_ATTRIBUTE,
- Issue.SOURCE_STAMP_UNKNOWN_ATTRIBUTE);
- sVerificationIssueIdToIssue.put(ApkVerificationIssue.SOURCE_STAMP_MALFORMED_LINEAGE,
- Issue.SOURCE_STAMP_MALFORMED_LINEAGE);
- sVerificationIssueIdToIssue.put(ApkVerificationIssue.SOURCE_STAMP_POR_CERT_MISMATCH,
- Issue.SOURCE_STAMP_POR_CERT_MISMATCH);
- sVerificationIssueIdToIssue.put(ApkVerificationIssue.SOURCE_STAMP_POR_DID_NOT_VERIFY,
- Issue.SOURCE_STAMP_POR_DID_NOT_VERIFY);
- sVerificationIssueIdToIssue.put(ApkVerificationIssue.JAR_SIG_NO_SIGNATURES,
- Issue.JAR_SIG_NO_SIGNATURES);
- sVerificationIssueIdToIssue.put(ApkVerificationIssue.JAR_SIG_PARSE_EXCEPTION,
- Issue.JAR_SIG_PARSE_EXCEPTION);
- }
-
- /**
- * Converts the provided {@code verificationIssues} to a {@code List} of corresponding
- * {@link IssueWithParams} instances.
- */
- public static List getIssuesFromVerificationIssues(
- List extends ApkVerificationIssue> verificationIssues) {
- List result = new ArrayList<>(verificationIssues.size());
- for (ApkVerificationIssue issue : verificationIssues) {
- if (issue instanceof IssueWithParams) {
- result.add((IssueWithParams) issue);
- } else {
- result.add(
- new IssueWithParams(sVerificationIssueIdToIssue.get(issue.getIssueId()),
- issue.getParams()));
- }
- }
- return result;
- }
- }
-}
diff --git a/app/src/main/java/com/android/apksig/Constants.java b/app/src/main/java/com/android/apksig/Constants.java
deleted file mode 100644
index 32c7375c09..0000000000
--- a/app/src/main/java/com/android/apksig/Constants.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.apksig;
-
-import com.android.apksig.internal.apk.stamp.SourceStampConstants;
-import com.android.apksig.internal.apk.v1.V1SchemeConstants;
-import com.android.apksig.internal.apk.v2.V2SchemeConstants;
-import com.android.apksig.internal.apk.v3.V3SchemeConstants;
-
-/**
- * Exports internally defined constants to allow clients to reference these values without relying
- * on internal code.
- */
-public class Constants {
- private Constants() {}
-
- public static final int VERSION_SOURCE_STAMP = 0;
- public static final int VERSION_JAR_SIGNATURE_SCHEME = 1;
- public static final int VERSION_APK_SIGNATURE_SCHEME_V2 = 2;
- public static final int VERSION_APK_SIGNATURE_SCHEME_V3 = 3;
- public static final int VERSION_APK_SIGNATURE_SCHEME_V4 = 4;
-
- public static final String MANIFEST_ENTRY_NAME = V1SchemeConstants.MANIFEST_ENTRY_NAME;
-
- public static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID =
- V2SchemeConstants.APK_SIGNATURE_SCHEME_V2_BLOCK_ID;
-
- public static final int APK_SIGNATURE_SCHEME_V3_BLOCK_ID =
- V3SchemeConstants.APK_SIGNATURE_SCHEME_V3_BLOCK_ID;
- public static final int PROOF_OF_ROTATION_ATTR_ID = V3SchemeConstants.PROOF_OF_ROTATION_ATTR_ID;
-
- public static final int V1_SOURCE_STAMP_BLOCK_ID =
- SourceStampConstants.V1_SOURCE_STAMP_BLOCK_ID;
- public static final int V2_SOURCE_STAMP_BLOCK_ID =
- SourceStampConstants.V2_SOURCE_STAMP_BLOCK_ID;
-
- public static final String OID_RSA_ENCRYPTION = "1.2.840.113549.1.1.1";
-}
diff --git a/app/src/main/java/com/android/apksig/DefaultApkSignerEngine.java b/app/src/main/java/com/android/apksig/DefaultApkSignerEngine.java
deleted file mode 100644
index e2256dadb9..0000000000
--- a/app/src/main/java/com/android/apksig/DefaultApkSignerEngine.java
+++ /dev/null
@@ -1,1844 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.apksig;
-
-import static com.android.apksig.apk.ApkUtils.SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME;
-import static com.android.apksig.apk.ApkUtils.computeSha256DigestBytes;
-import static com.android.apksig.internal.apk.ApkSigningBlockUtils.VERITY_PADDING_BLOCK_ID;
-import static com.android.apksig.internal.apk.ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2;
-import static com.android.apksig.internal.apk.ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3;
-import static com.android.apksig.internal.apk.ApkSigningBlockUtils.VERSION_JAR_SIGNATURE_SCHEME;
-
-import com.android.apksig.apk.ApkFormatException;
-import com.android.apksig.apk.ApkUtils;
-import com.android.apksig.internal.apk.ApkSigningBlockUtils;
-import com.android.apksig.internal.apk.ContentDigestAlgorithm;
-import com.android.apksig.internal.apk.SignatureAlgorithm;
-import com.android.apksig.internal.apk.stamp.V2SourceStampSigner;
-import com.android.apksig.internal.apk.v1.DigestAlgorithm;
-import com.android.apksig.internal.apk.v1.V1SchemeConstants;
-import com.android.apksig.internal.apk.v1.V1SchemeSigner;
-import com.android.apksig.internal.apk.v1.V1SchemeVerifier;
-import com.android.apksig.internal.apk.v2.V2SchemeSigner;
-import com.android.apksig.internal.apk.v3.V3SchemeSigner;
-import com.android.apksig.internal.apk.v4.V4SchemeSigner;
-import com.android.apksig.internal.apk.v4.V4Signature;
-import com.android.apksig.internal.jar.ManifestParser;
-import com.android.apksig.internal.util.AndroidSdkVersion;
-import com.android.apksig.internal.util.Pair;
-import com.android.apksig.internal.util.TeeDataSink;
-import com.android.apksig.util.DataSink;
-import com.android.apksig.util.DataSinks;
-import com.android.apksig.util.DataSource;
-import com.android.apksig.util.RunnablesExecutor;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.ByteBuffer;
-import java.security.InvalidKeyException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SignatureException;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Default implementation of {@link ApkSignerEngine}.
- *
- * Use {@link Builder} to obtain instances of this engine.
- */
-public class DefaultApkSignerEngine implements ApkSignerEngine {
-
- // IMPLEMENTATION NOTE: This engine generates a signed APK as follows:
- // 1. The engine asks its client to output input JAR entries which are not part of JAR
- // signature.
- // 2. If JAR signing (v1 signing) is enabled, the engine inspects the output JAR entries to
- // compute their digests, to be placed into output META-INF/MANIFEST.MF. It also inspects
- // the contents of input and output META-INF/MANIFEST.MF to borrow the main section of the
- // file. It does not care about individual (i.e., JAR entry-specific) sections. It then
- // emits the v1 signature (a set of JAR entries) and asks the client to output them.
- // 3. If APK Signature Scheme v2 (v2 signing) is enabled, the engine emits an APK Signing Block
- // from outputZipSections() and asks its client to insert this block into the output.
- // 4. If APK Signature Scheme v3 (v3 signing) is enabled, the engine includes it in the APK
- // Signing BLock output from outputZipSections() and asks its client to insert this block
- // into the output. If both v2 and v3 signing is enabled, they are both added to the APK
- // Signing Block before asking the client to insert it into the output.
-
- private final boolean mV1SigningEnabled;
- private final boolean mV2SigningEnabled;
- private final boolean mV3SigningEnabled;
- private final boolean mVerityEnabled;
- private final boolean mDebuggableApkPermitted;
- private final boolean mOtherSignersSignaturesPreserved;
- private final String mCreatedBy;
- private final List mSignerConfigs;
- private final SignerConfig mSourceStampSignerConfig;
- private final SigningCertificateLineage mSourceStampSigningCertificateLineage;
- private final int mMinSdkVersion;
- private final SigningCertificateLineage mSigningCertificateLineage;
-
- private List mPreservedV2Signers = Collections.emptyList();
- private List> mPreservedSignatureBlocks = Collections.emptyList();
-
- private List mV1SignerConfigs = Collections.emptyList();
- private DigestAlgorithm mV1ContentDigestAlgorithm;
-
- private boolean mClosed;
-
- private boolean mV1SignaturePending;
-
- /** Names of JAR entries which this engine is expected to output as part of v1 signing. */
- private Set mSignatureExpectedOutputJarEntryNames = Collections.emptySet();
-
- /** Requests for digests of output JAR entries. */
- private final Map mOutputJarEntryDigestRequests =
- new HashMap<>();
-
- /** Digests of output JAR entries. */
- private final Map mOutputJarEntryDigests = new HashMap<>();
-
- /** Data of JAR entries emitted by this engine as v1 signature. */
- private final Map mEmittedSignatureJarEntryData = new HashMap<>();
-
- /** Requests for data of output JAR entries which comprise the v1 signature. */
- private final Map mOutputSignatureJarEntryDataRequests =
- new HashMap<>();
- /**
- * Request to obtain the data of MANIFEST.MF or {@code null} if the request hasn't been issued.
- */
- private GetJarEntryDataRequest mInputJarManifestEntryDataRequest;
-
- /**
- * Request to obtain the data of AndroidManifest.xml or {@code null} if the request hasn't been
- * issued.
- */
- private GetJarEntryDataRequest mOutputAndroidManifestEntryDataRequest;
-
- /**
- * Whether the package being signed is marked as {@code android:debuggable} or {@code null} if
- * this is not yet known.
- */
- private Boolean mDebuggable;
-
- /**
- * Request to output the emitted v1 signature or {@code null} if the request hasn't been issued.
- */
- private OutputJarSignatureRequestImpl mAddV1SignatureRequest;
-
- private boolean mV2SignaturePending;
- private boolean mV3SignaturePending;
-
- /**
- * Request to output the emitted v2 and/or v3 signature(s) {@code null} if the request hasn't
- * been issued.
- */
- private OutputApkSigningBlockRequestImpl mAddSigningBlockRequest;
-
- private RunnablesExecutor mExecutor = RunnablesExecutor.MULTI_THREADED;
-
- /**
- * A Set of block IDs to be discarded when requesting to preserve the original signatures.
- */
- private static final Set DISCARDED_SIGNATURE_BLOCK_IDS;
- static {
- DISCARDED_SIGNATURE_BLOCK_IDS = new HashSet<>(3);
- // The verity padding block is recomputed on an
- // ApkSigningBlockUtils.ANDROID_COMMON_PAGE_ALIGNMENT_BYTES boundary.
- DISCARDED_SIGNATURE_BLOCK_IDS.add(VERITY_PADDING_BLOCK_ID);
- // The source stamp block is not currently preserved; appending a new signature scheme
- // block will invalidate the previous source stamp.
- DISCARDED_SIGNATURE_BLOCK_IDS.add(Constants.V1_SOURCE_STAMP_BLOCK_ID);
- DISCARDED_SIGNATURE_BLOCK_IDS.add(Constants.V2_SOURCE_STAMP_BLOCK_ID);
- }
-
- private DefaultApkSignerEngine(
- List signerConfigs,
- SignerConfig sourceStampSignerConfig,
- SigningCertificateLineage sourceStampSigningCertificateLineage,
- int minSdkVersion,
- boolean v1SigningEnabled,
- boolean v2SigningEnabled,
- boolean v3SigningEnabled,
- boolean verityEnabled,
- boolean debuggableApkPermitted,
- boolean otherSignersSignaturesPreserved,
- String createdBy,
- SigningCertificateLineage signingCertificateLineage)
- throws InvalidKeyException {
- if (signerConfigs.isEmpty()) {
- throw new IllegalArgumentException("At least one signer config must be provided");
- }
-
- mV1SigningEnabled = v1SigningEnabled;
- mV2SigningEnabled = v2SigningEnabled;
- mV3SigningEnabled = v3SigningEnabled;
- mVerityEnabled = verityEnabled;
- mV1SignaturePending = v1SigningEnabled;
- mV2SignaturePending = v2SigningEnabled;
- mV3SignaturePending = v3SigningEnabled;
- mDebuggableApkPermitted = debuggableApkPermitted;
- mOtherSignersSignaturesPreserved = otherSignersSignaturesPreserved;
- mCreatedBy = createdBy;
- mSignerConfigs = signerConfigs;
- mSourceStampSignerConfig = sourceStampSignerConfig;
- mSourceStampSigningCertificateLineage = sourceStampSigningCertificateLineage;
- mMinSdkVersion = minSdkVersion;
- mSigningCertificateLineage = signingCertificateLineage;
-
- if (v1SigningEnabled) {
- if (v3SigningEnabled) {
-
- // v3 signing only supports single signers, of which the oldest (first) will be the
- // one to use for v1 and v2 signing
- SignerConfig oldestConfig = signerConfigs.get(0);
-
- // in the event of signing certificate changes, make sure we have the oldest in the
- // signing history to sign with v1
- if (signingCertificateLineage != null) {
- SigningCertificateLineage subLineage =
- signingCertificateLineage.getSubLineage(
- oldestConfig.mCertificates.get(0));
- if (subLineage.size() != 1) {
- throw new IllegalArgumentException(
- "v1 signing enabled but the oldest signer in the"
- + " SigningCertificateLineage is missing. Please provide the"
- + " oldest signer to enable v1 signing");
- }
- }
- createV1SignerConfigs(Collections.singletonList(oldestConfig), minSdkVersion);
- } else {
- createV1SignerConfigs(signerConfigs, minSdkVersion);
- }
- }
- }
-
- private void createV1SignerConfigs(List signerConfigs, int minSdkVersion)
- throws InvalidKeyException {
- mV1SignerConfigs = new ArrayList<>(signerConfigs.size());
- Map v1SignerNameToSignerIndex = new HashMap<>(signerConfigs.size());
- DigestAlgorithm v1ContentDigestAlgorithm = null;
- for (int i = 0; i < signerConfigs.size(); i++) {
- SignerConfig signerConfig = signerConfigs.get(i);
- List certificates = signerConfig.getCertificates();
- PublicKey publicKey = certificates.get(0).getPublicKey();
-
- String v1SignerName = V1SchemeSigner.getSafeSignerName(signerConfig.getName());
- // Check whether the signer's name is unique among all v1 signers
- Integer indexOfOtherSignerWithSameName = v1SignerNameToSignerIndex.put(v1SignerName, i);
- if (indexOfOtherSignerWithSameName != null) {
- throw new IllegalArgumentException(
- "Signers #"
- + (indexOfOtherSignerWithSameName + 1)
- + " and #"
- + (i + 1)
- + " have the same name: "
- + v1SignerName
- + ". v1 signer names must be unique");
- }
-
- DigestAlgorithm v1SignatureDigestAlgorithm =
- V1SchemeSigner.getSuggestedSignatureDigestAlgorithm(publicKey, minSdkVersion);
- V1SchemeSigner.SignerConfig v1SignerConfig = new V1SchemeSigner.SignerConfig();
- v1SignerConfig.name = v1SignerName;
- v1SignerConfig.privateKey = signerConfig.getPrivateKey();
- v1SignerConfig.certificates = certificates;
- v1SignerConfig.signatureDigestAlgorithm = v1SignatureDigestAlgorithm;
- v1SignerConfig.deterministicDsaSigning = signerConfig.getDeterministicDsaSigning();
- // For digesting contents of APK entries and of MANIFEST.MF, pick the algorithm
- // of comparable strength to the digest algorithm used for computing the signature.
- // When there are multiple signers, pick the strongest digest algorithm out of their
- // signature digest algorithms. This avoids reducing the digest strength used by any
- // of the signers to protect APK contents.
- if (v1ContentDigestAlgorithm == null) {
- v1ContentDigestAlgorithm = v1SignatureDigestAlgorithm;
- } else {
- if (DigestAlgorithm.BY_STRENGTH_COMPARATOR.compare(
- v1SignatureDigestAlgorithm, v1ContentDigestAlgorithm)
- > 0) {
- v1ContentDigestAlgorithm = v1SignatureDigestAlgorithm;
- }
- }
- mV1SignerConfigs.add(v1SignerConfig);
- }
- mV1ContentDigestAlgorithm = v1ContentDigestAlgorithm;
- mSignatureExpectedOutputJarEntryNames =
- V1SchemeSigner.getOutputEntryNames(mV1SignerConfigs);
- }
-
- private List createV2SignerConfigs(
- boolean apkSigningBlockPaddingSupported) throws InvalidKeyException {
- if (mV3SigningEnabled) {
-
- // v3 signing only supports single signers, of which the oldest (first) will be the one
- // to use for v1 and v2 signing
- List signerConfig = new ArrayList<>();
-
- SignerConfig oldestConfig = mSignerConfigs.get(0);
-
- // first make sure that if we have signing certificate history that the oldest signer
- // corresponds to the oldest ancestor
- if (mSigningCertificateLineage != null) {
- SigningCertificateLineage subLineage =
- mSigningCertificateLineage.getSubLineage(oldestConfig.mCertificates.get(0));
- if (subLineage.size() != 1) {
- throw new IllegalArgumentException(
- "v2 signing enabled but the oldest signer in"
- + " the SigningCertificateLineage is missing. Please provide"
- + " the oldest signer to enable v2 signing.");
- }
- }
- signerConfig.add(
- createSigningBlockSignerConfig(
- mSignerConfigs.get(0),
- apkSigningBlockPaddingSupported,
- ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2));
- return signerConfig;
- } else {
- return createSigningBlockSignerConfigs(
- apkSigningBlockPaddingSupported,
- ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2);
- }
- }
-
- private List processV3Configs(
- List rawConfigs) throws InvalidKeyException {
- List processedConfigs = new ArrayList<>();
-
- // we have our configs, now touch them up to appropriately cover all SDK levels since APK
- // signature scheme v3 was introduced
- int currentMinSdk = Integer.MAX_VALUE;
- for (int i = rawConfigs.size() - 1; i >= 0; i--) {
- ApkSigningBlockUtils.SignerConfig config = rawConfigs.get(i);
- if (config.signatureAlgorithms == null) {
- // no valid algorithm was found for this signer, and we haven't yet covered all
- // platform versions, something's wrong
- String keyAlgorithm = config.certificates.get(0).getPublicKey().getAlgorithm();
- throw new InvalidKeyException(
- "Unsupported key algorithm "
- + keyAlgorithm
- + " is "
- + "not supported for APK Signature Scheme v3 signing");
- }
- if (i == rawConfigs.size() - 1) {
- // first go through the loop, config should support all future platform versions.
- // this assumes we don't deprecate support for signers in the future. If we do,
- // this needs to change
- config.maxSdkVersion = Integer.MAX_VALUE;
- } else {
- // otherwise, we only want to use this signer up to the minimum platform version
- // on which a newer one is acceptable
- config.maxSdkVersion = currentMinSdk - 1;
- }
- config.minSdkVersion = getMinSdkFromV3SignatureAlgorithms(config.signatureAlgorithms);
- if (mSigningCertificateLineage != null) {
- config.mSigningCertificateLineage =
- mSigningCertificateLineage.getSubLineage(config.certificates.get(0));
- }
- // we know that this config will be used, so add it to our result, order doesn't matter
- // at this point (and likely only one will be needed
- processedConfigs.add(config);
- currentMinSdk = config.minSdkVersion;
- if (currentMinSdk <= mMinSdkVersion || currentMinSdk <= AndroidSdkVersion.P) {
- // this satisfies all we need, stop here
- break;
- }
- }
- if (currentMinSdk > AndroidSdkVersion.P && currentMinSdk > mMinSdkVersion) {
- // we can't cover all desired SDK versions, abort
- throw new InvalidKeyException(
- "Provided key algorithms not supported on all desired "
- + "Android SDK versions");
- }
-
- return processedConfigs;
- }
-
- private List createV3SignerConfigs(
- boolean apkSigningBlockPaddingSupported) throws InvalidKeyException {
- return processV3Configs(createSigningBlockSignerConfigs(apkSigningBlockPaddingSupported,
- ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3));
- }
-
- private ApkSigningBlockUtils.SignerConfig createV4SignerConfig() throws InvalidKeyException {
- List configs = createSigningBlockSignerConfigs(true,
- ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V4);
- if (configs.size() != 1) {
- // V4 only uses signer config to connect back to v3. Use the same filtering logic.
- configs = processV3Configs(configs);
- }
- if (configs.size() != 1) {
- throw new InvalidKeyException("Only accepting one signer config for V4 Signature.");
- }
- return configs.get(0);
- }
-
- private ApkSigningBlockUtils.SignerConfig createSourceStampSignerConfig()
- throws InvalidKeyException {
- ApkSigningBlockUtils.SignerConfig config = createSigningBlockSignerConfig(
- mSourceStampSignerConfig,
- /* apkSigningBlockPaddingSupported= */ false,
- ApkSigningBlockUtils.VERSION_SOURCE_STAMP);
- if (mSourceStampSigningCertificateLineage != null) {
- config.mSigningCertificateLineage = mSourceStampSigningCertificateLineage.getSubLineage(
- config.certificates.get(0));
- }
- return config;
- }
-
- private int getMinSdkFromV3SignatureAlgorithms(List algorithms) {
- int min = Integer.MAX_VALUE;
- for (SignatureAlgorithm algorithm : algorithms) {
- int current = algorithm.getMinSdkVersion();
- if (current < min) {
- if (current <= mMinSdkVersion || current <= AndroidSdkVersion.P) {
- // this algorithm satisfies all of our needs, no need to keep looking
- return current;
- } else {
- min = current;
- }
- }
- }
- return min;
- }
-
- private List createSigningBlockSignerConfigs(
- boolean apkSigningBlockPaddingSupported, int schemeId) throws InvalidKeyException {
- List signerConfigs =
- new ArrayList<>(mSignerConfigs.size());
- for (int i = 0; i < mSignerConfigs.size(); i++) {
- SignerConfig signerConfig = mSignerConfigs.get(i);
- signerConfigs.add(
- createSigningBlockSignerConfig(
- signerConfig, apkSigningBlockPaddingSupported, schemeId));
- }
- return signerConfigs;
- }
-
- private ApkSigningBlockUtils.SignerConfig createSigningBlockSignerConfig(
- SignerConfig signerConfig, boolean apkSigningBlockPaddingSupported, int schemeId)
- throws InvalidKeyException {
- List certificates = signerConfig.getCertificates();
- PublicKey publicKey = certificates.get(0).getPublicKey();
-
- ApkSigningBlockUtils.SignerConfig newSignerConfig = new ApkSigningBlockUtils.SignerConfig();
- newSignerConfig.privateKey = signerConfig.getPrivateKey();
- newSignerConfig.certificates = certificates;
-
- switch (schemeId) {
- case ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2:
- newSignerConfig.signatureAlgorithms =
- V2SchemeSigner.getSuggestedSignatureAlgorithms(
- publicKey,
- mMinSdkVersion,
- apkSigningBlockPaddingSupported && mVerityEnabled,
- signerConfig.getDeterministicDsaSigning());
- break;
- case ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3:
- try {
- newSignerConfig.signatureAlgorithms =
- V3SchemeSigner.getSuggestedSignatureAlgorithms(
- publicKey,
- mMinSdkVersion,
- apkSigningBlockPaddingSupported && mVerityEnabled,
- signerConfig.getDeterministicDsaSigning());
- } catch (InvalidKeyException e) {
-
- // It is possible for a signer used for v1/v2 signing to not be allowed for use
- // with v3 signing. This is ok as long as there exists a more recent v3 signer
- // that covers all supported platform versions. Populate signatureAlgorithm
- // with null, it will be cleaned-up in a later step.
- newSignerConfig.signatureAlgorithms = null;
- }
- break;
- case ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V4:
- try {
- newSignerConfig.signatureAlgorithms =
- V4SchemeSigner.getSuggestedSignatureAlgorithms(
- publicKey, mMinSdkVersion, apkSigningBlockPaddingSupported,
- signerConfig.getDeterministicDsaSigning());
- } catch (InvalidKeyException e) {
- // V4 is an optional signing schema, ok to proceed without.
- newSignerConfig.signatureAlgorithms = null;
- }
- break;
- case ApkSigningBlockUtils.VERSION_SOURCE_STAMP:
- newSignerConfig.signatureAlgorithms =
- Collections.singletonList(
- SignatureAlgorithm.RSA_PKCS1_V1_5_WITH_SHA256);
- break;
- default:
- throw new IllegalArgumentException("Unknown APK Signature Scheme ID requested");
- }
- return newSignerConfig;
- }
-
- private boolean isDebuggable(String entryName) {
- return mDebuggableApkPermitted
- || !ApkUtils.ANDROID_MANIFEST_ZIP_ENTRY_NAME.equals(entryName);
- }
-
- /**
- * Initializes DefaultApkSignerEngine with the existing MANIFEST.MF. This reads existing digests
- * from the MANIFEST.MF file (they are assumed correct) and stores them for the final signature
- * without recalculation. This step has a significant performance benefit in case of incremental
- * build.
- *
- * This method extracts and stored computed digest for every entry that it would compute it
- * for in the {@link #outputJarEntry(String)} method
- *
- * @param manifestBytes raw representation of MANIFEST.MF file
- * @param entryNames a set of expected entries names
- * @return set of entry names which were processed by the engine during the initialization, a
- * subset of entryNames
- */
- @Override
- @SuppressWarnings("AndroidJdkLibsChecker")
- public Set initWith(byte[] manifestBytes, Set entryNames) {
- V1SchemeVerifier.Result result = new V1SchemeVerifier.Result();
- Pair> sections =
- V1SchemeVerifier.parseManifest(manifestBytes, entryNames, result);
- String alg = V1SchemeSigner.getJcaMessageDigestAlgorithm(mV1ContentDigestAlgorithm);
- for (Map.Entry entry : sections.getSecond().entrySet()) {
- String entryName = entry.getKey();
- if (V1SchemeSigner.isJarEntryDigestNeededInManifest(entry.getKey())
- && isDebuggable(entryName)) {
-
- V1SchemeVerifier.NamedDigest extractedDigest = null;
- Collection digestsToVerify =
- V1SchemeVerifier.getDigestsToVerify(
- entry.getValue(), "-Digest", mMinSdkVersion, Integer.MAX_VALUE);
- for (V1SchemeVerifier.NamedDigest digestToVerify : digestsToVerify) {
- if (digestToVerify.jcaDigestAlgorithm.equals(alg)) {
- extractedDigest = digestToVerify;
- break;
- }
- }
- if (extractedDigest != null) {
- mOutputJarEntryDigests.put(entryName, extractedDigest.digest);
- }
- }
- }
- return mOutputJarEntryDigests.keySet();
- }
-
- @Override
- public void setExecutor(RunnablesExecutor executor) {
- mExecutor = executor;
- }
-
- @Override
- public void inputApkSigningBlock(DataSource apkSigningBlock) {
- checkNotClosed();
-
- if ((apkSigningBlock == null) || (apkSigningBlock.size() == 0)) {
- return;
- }
-
- if (mOtherSignersSignaturesPreserved) {
- boolean schemeSignatureBlockPreserved = false;
- mPreservedSignatureBlocks = new ArrayList<>();
- try {
- List> signatureBlocks =
- ApkSigningBlockUtils.getApkSignatureBlocks(apkSigningBlock);
- for (Pair signatureBlock : signatureBlocks) {
- if (signatureBlock.getSecond() == Constants.APK_SIGNATURE_SCHEME_V2_BLOCK_ID) {
- // If a V2 signature block is found and the engine is configured to use V2
- // then save any of the previous signers that are not part of the current
- // signing request.
- if (mV2SigningEnabled) {
- List, byte[]>> v2Signers =
- ApkSigningBlockUtils.getApkSignatureBlockSigners(
- signatureBlock.getFirst());
- mPreservedV2Signers = new ArrayList<>(v2Signers.size());
- for (Pair, byte[]> v2Signer : v2Signers) {
- if (!isConfiguredWithSigner(v2Signer.getFirst())) {
- mPreservedV2Signers.add(v2Signer.getSecond());
- schemeSignatureBlockPreserved = true;
- }
- }
- } else {
- // else V2 signing is not enabled; save the entire signature block to be
- // added to the final APK signing block.
- mPreservedSignatureBlocks.add(signatureBlock);
- schemeSignatureBlockPreserved = true;
- }
- } else if (signatureBlock.getSecond()
- == Constants.APK_SIGNATURE_SCHEME_V3_BLOCK_ID) {
- // Preserving other signers in the presence of a V3 signature block is only
- // supported if the engine is configured to resign the APK with the V3
- // signature scheme, and the V3 signer in the signature block is the same
- // as the engine is configured to use.
- if (!mV3SigningEnabled) {
- throw new IllegalStateException(
- "Preserving an existing V3 signature is not supported");
- }
- List, byte[]>> v3Signers =
- ApkSigningBlockUtils.getApkSignatureBlockSigners(
- signatureBlock.getFirst());
- if (v3Signers.size() > 1) {
- throw new IllegalArgumentException(
- "The provided APK signing block contains " + v3Signers.size()
- + " V3 signers; the V3 signature scheme only supports"
- + " one signer");
- }
- // If there is only a single V3 signer then ensure it is the signer
- // configured to sign the APK.
- if (v3Signers.size() == 1
- && !isConfiguredWithSigner(v3Signers.get(0).getFirst())) {
- throw new IllegalStateException(
- "The V3 signature scheme only supports one signer; a request "
- + "was made to preserve the existing V3 signature, "
- + "but the engine is configured to sign with a "
- + "different signer");
- }
- } else if (!DISCARDED_SIGNATURE_BLOCK_IDS.contains(
- signatureBlock.getSecond())) {
- mPreservedSignatureBlocks.add(signatureBlock);
- }
- }
- } catch (ApkFormatException | CertificateException | IOException e) {
- throw new IllegalArgumentException("Unable to parse the provided signing block", e);
- }
- // Signature scheme V3+ only support a single signer; if the engine is configured to
- // sign with V3+ then ensure no scheme signature blocks have been preserved.
- if (mV3SigningEnabled && schemeSignatureBlockPreserved) {
- throw new IllegalStateException(
- "Signature scheme V3+ only supports a single signer and cannot be "
- + "appended to the existing signature scheme blocks");
- }
- return;
- }
- }
-
- /**
- * Returns whether the engine is configured to sign the APK with a signer using the specified
- * {@code signerCerts}.
- */
- private boolean isConfiguredWithSigner(List signerCerts) {
- for (SignerConfig signerConfig : mSignerConfigs) {
- if (signerCerts.containsAll(signerConfig.getCertificates())) {
- return true;
- }
- }
- return false;
- }
-
- @Override
- public InputJarEntryInstructions inputJarEntry(String entryName) {
- checkNotClosed();
-
- InputJarEntryInstructions.OutputPolicy outputPolicy =
- getInputJarEntryOutputPolicy(entryName);
- switch (outputPolicy) {
- case SKIP:
- return new InputJarEntryInstructions(InputJarEntryInstructions.OutputPolicy.SKIP);
- case OUTPUT:
- return new InputJarEntryInstructions(InputJarEntryInstructions.OutputPolicy.OUTPUT);
- case OUTPUT_BY_ENGINE:
- if (V1SchemeConstants.MANIFEST_ENTRY_NAME.equals(entryName)) {
- // We copy the main section of the JAR manifest from input to output. Thus, this
- // invalidates v1 signature and we need to see the entry's data.
- mInputJarManifestEntryDataRequest = new GetJarEntryDataRequest(entryName);
- return new InputJarEntryInstructions(
- InputJarEntryInstructions.OutputPolicy.OUTPUT_BY_ENGINE,
- mInputJarManifestEntryDataRequest);
- }
- return new InputJarEntryInstructions(
- InputJarEntryInstructions.OutputPolicy.OUTPUT_BY_ENGINE);
- default:
- throw new RuntimeException("Unsupported output policy: " + outputPolicy);
- }
- }
-
- @Override
- public InspectJarEntryRequest outputJarEntry(String entryName) {
- checkNotClosed();
- invalidateV2Signature();
-
- if (!isDebuggable(entryName)) {
- forgetOutputApkDebuggableStatus();
- }
-
- if (!mV1SigningEnabled) {
- // No need to inspect JAR entries when v1 signing is not enabled.
- if (!isDebuggable(entryName)) {
- // To reject debuggable APKs we need to inspect the APK's AndroidManifest.xml to
- // check whether it declares that the APK is debuggable
- mOutputAndroidManifestEntryDataRequest = new GetJarEntryDataRequest(entryName);
- return mOutputAndroidManifestEntryDataRequest;
- }
- return null;
- }
- // v1 signing is enabled
-
- if (V1SchemeSigner.isJarEntryDigestNeededInManifest(entryName)) {
- // This entry is covered by v1 signature. We thus need to inspect the entry's data to
- // compute its digest(s) for v1 signature.
-
- // TODO: Handle the case where other signer's v1 signatures are present and need to be
- // preserved. In that scenario we can't modify MANIFEST.MF and add/remove JAR entries
- // covered by v1 signature.
- invalidateV1Signature();
- GetJarEntryDataDigestRequest dataDigestRequest =
- new GetJarEntryDataDigestRequest(
- entryName,
- V1SchemeSigner.getJcaMessageDigestAlgorithm(mV1ContentDigestAlgorithm));
- mOutputJarEntryDigestRequests.put(entryName, dataDigestRequest);
- mOutputJarEntryDigests.remove(entryName);
-
- if ((!mDebuggableApkPermitted)
- && (ApkUtils.ANDROID_MANIFEST_ZIP_ENTRY_NAME.equals(entryName))) {
- // To reject debuggable APKs we need to inspect the APK's AndroidManifest.xml to
- // check whether it declares that the APK is debuggable
- mOutputAndroidManifestEntryDataRequest = new GetJarEntryDataRequest(entryName);
- return new CompoundInspectJarEntryRequest(
- entryName, mOutputAndroidManifestEntryDataRequest, dataDigestRequest);
- }
-
- return dataDigestRequest;
- }
-
- if (mSignatureExpectedOutputJarEntryNames.contains(entryName)) {
- // This entry is part of v1 signature generated by this engine. We need to check whether
- // the entry's data is as output by the engine.
- invalidateV1Signature();
- GetJarEntryDataRequest dataRequest;
- if (V1SchemeConstants.MANIFEST_ENTRY_NAME.equals(entryName)) {
- dataRequest = new GetJarEntryDataRequest(entryName);
- mInputJarManifestEntryDataRequest = dataRequest;
- } else {
- // If this entry is part of v1 signature which has been emitted by this engine,
- // check whether the output entry's data matches what the engine emitted.
- dataRequest =
- (mEmittedSignatureJarEntryData.containsKey(entryName))
- ? new GetJarEntryDataRequest(entryName)
- : null;
- }
-
- if (dataRequest != null) {
- mOutputSignatureJarEntryDataRequests.put(entryName, dataRequest);
- }
- return dataRequest;
- }
-
- // This entry is not covered by v1 signature and isn't part of v1 signature.
- return null;
- }
-
- @Override
- public InputJarEntryInstructions.OutputPolicy inputJarEntryRemoved(String entryName) {
- checkNotClosed();
- return getInputJarEntryOutputPolicy(entryName);
- }
-
- @Override
- public void outputJarEntryRemoved(String entryName) {
- checkNotClosed();
- invalidateV2Signature();
- if (!mV1SigningEnabled) {
- return;
- }
-
- if (V1SchemeSigner.isJarEntryDigestNeededInManifest(entryName)) {
- // This entry is covered by v1 signature.
- invalidateV1Signature();
- mOutputJarEntryDigests.remove(entryName);
- mOutputJarEntryDigestRequests.remove(entryName);
- mOutputSignatureJarEntryDataRequests.remove(entryName);
- return;
- }
-
- if (mSignatureExpectedOutputJarEntryNames.contains(entryName)) {
- // This entry is part of the v1 signature generated by this engine.
- invalidateV1Signature();
- return;
- }
- }
-
- @Override
- public OutputJarSignatureRequest outputJarEntries()
- throws ApkFormatException, InvalidKeyException, SignatureException,
- NoSuchAlgorithmException {
- checkNotClosed();
-
- if (!mV1SignaturePending) {
- return null;
- }
-
- if ((mInputJarManifestEntryDataRequest != null)
- && (!mInputJarManifestEntryDataRequest.isDone())) {
- throw new IllegalStateException(
- "Still waiting to inspect input APK's "
- + mInputJarManifestEntryDataRequest.getEntryName());
- }
-
- for (GetJarEntryDataDigestRequest digestRequest : mOutputJarEntryDigestRequests.values()) {
- String entryName = digestRequest.getEntryName();
- if (!digestRequest.isDone()) {
- throw new IllegalStateException(
- "Still waiting to inspect output APK's " + entryName);
- }
- mOutputJarEntryDigests.put(entryName, digestRequest.getDigest());
- }
- if (isEligibleForSourceStamp()) {
- MessageDigest messageDigest =
- MessageDigest.getInstance(
- V1SchemeSigner.getJcaMessageDigestAlgorithm(mV1ContentDigestAlgorithm));
- messageDigest.update(generateSourceStampCertificateDigest());
- mOutputJarEntryDigests.put(
- SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME, messageDigest.digest());
- }
- mOutputJarEntryDigestRequests.clear();
-
- for (GetJarEntryDataRequest dataRequest : mOutputSignatureJarEntryDataRequests.values()) {
- if (!dataRequest.isDone()) {
- throw new IllegalStateException(
- "Still waiting to inspect output APK's " + dataRequest.getEntryName());
- }
- }
-
- List apkSigningSchemeIds = new ArrayList<>();
- if (mV2SigningEnabled) {
- apkSigningSchemeIds.add(ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2);
- }
- if (mV3SigningEnabled) {
- apkSigningSchemeIds.add(ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3);
- }
- byte[] inputJarManifest =
- (mInputJarManifestEntryDataRequest != null)
- ? mInputJarManifestEntryDataRequest.getData()
- : null;
- if (isEligibleForSourceStamp()) {
- inputJarManifest =
- V1SchemeSigner.generateManifestFile(
- mV1ContentDigestAlgorithm,
- mOutputJarEntryDigests,
- inputJarManifest)
- .contents;
- }
-
- // Check whether the most recently used signature (if present) is still fine.
- checkOutputApkNotDebuggableIfDebuggableMustBeRejected();
- List> signatureZipEntries;
- if ((mAddV1SignatureRequest == null) || (!mAddV1SignatureRequest.isDone())) {
- try {
- signatureZipEntries =
- V1SchemeSigner.sign(
- mV1SignerConfigs,
- mV1ContentDigestAlgorithm,
- mOutputJarEntryDigests,
- apkSigningSchemeIds,
- inputJarManifest,
- mCreatedBy);
- } catch (CertificateException e) {
- throw new SignatureException("Failed to generate v1 signature", e);
- }
- } else {
- V1SchemeSigner.OutputManifestFile newManifest =
- V1SchemeSigner.generateManifestFile(
- mV1ContentDigestAlgorithm, mOutputJarEntryDigests, inputJarManifest);
- byte[] emittedSignatureManifest =
- mEmittedSignatureJarEntryData.get(V1SchemeConstants.MANIFEST_ENTRY_NAME);
- if (!Arrays.equals(newManifest.contents, emittedSignatureManifest)) {
- // Emitted v1 signature is no longer valid.
- try {
- signatureZipEntries =
- V1SchemeSigner.signManifest(
- mV1SignerConfigs,
- mV1ContentDigestAlgorithm,
- apkSigningSchemeIds,
- mCreatedBy,
- newManifest);
- } catch (CertificateException e) {
- throw new SignatureException("Failed to generate v1 signature", e);
- }
- } else {
- // Emitted v1 signature is still valid. Check whether the signature is there in the
- // output.
- signatureZipEntries = new ArrayList<>();
- for (Map.Entry expectedOutputEntry :
- mEmittedSignatureJarEntryData.entrySet()) {
- String entryName = expectedOutputEntry.getKey();
- byte[] expectedData = expectedOutputEntry.getValue();
- GetJarEntryDataRequest actualDataRequest =
- mOutputSignatureJarEntryDataRequests.get(entryName);
- if (actualDataRequest == null) {
- // This signature entry hasn't been output.
- signatureZipEntries.add(Pair.of(entryName, expectedData));
- continue;
- }
- byte[] actualData = actualDataRequest.getData();
- if (!Arrays.equals(expectedData, actualData)) {
- signatureZipEntries.add(Pair.of(entryName, expectedData));
- }
- }
- if (signatureZipEntries.isEmpty()) {
- // v1 signature in the output is valid
- return null;
- }
- // v1 signature in the output is not valid.
- }
- }
-
- if (signatureZipEntries.isEmpty()) {
- // v1 signature in the output is valid
- mV1SignaturePending = false;
- return null;
- }
-
- List sigEntries =
- new ArrayList<>(signatureZipEntries.size());
- for (Pair entry : signatureZipEntries) {
- String entryName = entry.getFirst();
- byte[] entryData = entry.getSecond();
- sigEntries.add(new OutputJarSignatureRequest.JarEntry(entryName, entryData));
- mEmittedSignatureJarEntryData.put(entryName, entryData);
- }
- mAddV1SignatureRequest = new OutputJarSignatureRequestImpl(sigEntries);
- return mAddV1SignatureRequest;
- }
-
- @Deprecated
- @Override
- public OutputApkSigningBlockRequest outputZipSections(
- DataSource zipEntries, DataSource zipCentralDirectory, DataSource zipEocd)
- throws IOException, InvalidKeyException, SignatureException, NoSuchAlgorithmException {
- return outputZipSectionsInternal(zipEntries, zipCentralDirectory, zipEocd, false);
- }
-
- @Override
- public OutputApkSigningBlockRequest2 outputZipSections2(
- DataSource zipEntries, DataSource zipCentralDirectory, DataSource zipEocd)
- throws IOException, InvalidKeyException, SignatureException, NoSuchAlgorithmException {
- return outputZipSectionsInternal(zipEntries, zipCentralDirectory, zipEocd, true);
- }
-
- private OutputApkSigningBlockRequestImpl outputZipSectionsInternal(
- DataSource zipEntries,
- DataSource zipCentralDirectory,
- DataSource zipEocd,
- boolean apkSigningBlockPaddingSupported)
- throws IOException, InvalidKeyException, SignatureException, NoSuchAlgorithmException {
- checkNotClosed();
- checkV1SigningDoneIfEnabled();
- if (!mV2SigningEnabled && !mV3SigningEnabled && !isEligibleForSourceStamp()) {
- return null;
- }
- checkOutputApkNotDebuggableIfDebuggableMustBeRejected();
-
- // adjust to proper padding
- Pair paddingPair =
- ApkSigningBlockUtils.generateApkSigningBlockPadding(
- zipEntries, apkSigningBlockPaddingSupported);
- DataSource beforeCentralDir = paddingPair.getFirst();
- int padSizeBeforeApkSigningBlock = paddingPair.getSecond();
- DataSource eocd = ApkSigningBlockUtils.copyWithModifiedCDOffset(beforeCentralDir, zipEocd);
-
- List> signingSchemeBlocks = new ArrayList<>();
- ApkSigningBlockUtils.SigningSchemeBlockAndDigests v2SigningSchemeBlockAndDigests = null;
- ApkSigningBlockUtils.SigningSchemeBlockAndDigests v3SigningSchemeBlockAndDigests = null;
- // If the engine is configured to preserve previous signature blocks and any were found in
- // the existing APK signing block then add them to the list to be used to generate the
- // new APK signing block.
- if (mOtherSignersSignaturesPreserved && mPreservedSignatureBlocks != null
- && !mPreservedSignatureBlocks.isEmpty()) {
- signingSchemeBlocks.addAll(mPreservedSignatureBlocks);
- }
-
- // create APK Signature Scheme V2 Signature if requested
- if (mV2SigningEnabled) {
- invalidateV2Signature();
- List v2SignerConfigs =
- createV2SignerConfigs(apkSigningBlockPaddingSupported);
- v2SigningSchemeBlockAndDigests =
- V2SchemeSigner.generateApkSignatureSchemeV2Block(
- mExecutor,
- beforeCentralDir,
- zipCentralDirectory,
- eocd,
- v2SignerConfigs,
- mV3SigningEnabled,
- mOtherSignersSignaturesPreserved ? mPreservedV2Signers : null);
- signingSchemeBlocks.add(v2SigningSchemeBlockAndDigests.signingSchemeBlock);
- }
- if (mV3SigningEnabled) {
- invalidateV3Signature();
- List v3SignerConfigs =
- createV3SignerConfigs(apkSigningBlockPaddingSupported);
- v3SigningSchemeBlockAndDigests =
- V3SchemeSigner.generateApkSignatureSchemeV3Block(
- mExecutor,
- beforeCentralDir,
- zipCentralDirectory,
- eocd,
- v3SignerConfigs);
- signingSchemeBlocks.add(v3SigningSchemeBlockAndDigests.signingSchemeBlock);
- }
- if (isEligibleForSourceStamp()) {
- ApkSigningBlockUtils.SignerConfig sourceStampSignerConfig =
- createSourceStampSignerConfig();
- Map> signatureSchemeDigestInfos =
- new HashMap<>();
- if (mV3SigningEnabled) {
- signatureSchemeDigestInfos.put(
- VERSION_APK_SIGNATURE_SCHEME_V3, v3SigningSchemeBlockAndDigests.digestInfo);
- }
- if (mV2SigningEnabled) {
- signatureSchemeDigestInfos.put(
- VERSION_APK_SIGNATURE_SCHEME_V2, v2SigningSchemeBlockAndDigests.digestInfo);
- }
- if (mV1SigningEnabled) {
- Map v1SigningSchemeDigests = new HashMap<>();
- try {
- // Jar signing related variables must have been already populated at this point
- // if V1 signing is enabled since it is happening before computations on the APK
- // signing block (V2/V3/V4/SourceStamp signing).
- byte[] inputJarManifest =
- (mInputJarManifestEntryDataRequest != null)
- ? mInputJarManifestEntryDataRequest.getData()
- : null;
- byte[] jarManifest =
- V1SchemeSigner.generateManifestFile(
- mV1ContentDigestAlgorithm,
- mOutputJarEntryDigests,
- inputJarManifest)
- .contents;
- // The digest of the jar manifest does not need to be computed in chunks due to
- // the small size of the manifest.
- v1SigningSchemeDigests.put(
- ContentDigestAlgorithm.SHA256, computeSha256DigestBytes(jarManifest));
- } catch (ApkFormatException e) {
- throw new RuntimeException("Failed to generate manifest file", e);
- }
- signatureSchemeDigestInfos.put(
- VERSION_JAR_SIGNATURE_SCHEME, v1SigningSchemeDigests);
- }
- signingSchemeBlocks.add(
- V2SourceStampSigner.generateSourceStampBlock(
- sourceStampSignerConfig, signatureSchemeDigestInfos));
- }
-
- // create APK Signing Block with v2 and/or v3 and/or SourceStamp blocks
- byte[] apkSigningBlock = ApkSigningBlockUtils.generateApkSigningBlock(signingSchemeBlocks);
-
- mAddSigningBlockRequest =
- new OutputApkSigningBlockRequestImpl(apkSigningBlock, padSizeBeforeApkSigningBlock);
- return mAddSigningBlockRequest;
- }
-
- @Override
- public void outputDone() {
- checkNotClosed();
- checkV1SigningDoneIfEnabled();
- checkSigningBlockDoneIfEnabled();
- }
-
- @Override
- public void signV4(DataSource dataSource, File outputFile, boolean ignoreFailures)
- throws SignatureException {
- if (outputFile == null) {
- if (ignoreFailures) {
- return;
- }
- throw new SignatureException("Missing V4 output file.");
- }
- try {
- ApkSigningBlockUtils.SignerConfig v4SignerConfig = createV4SignerConfig();
- V4SchemeSigner.generateV4Signature(dataSource, v4SignerConfig, outputFile);
- } catch (InvalidKeyException | IOException | NoSuchAlgorithmException e) {
- if (ignoreFailures) {
- return;
- }
- throw new SignatureException("V4 signing failed", e);
- }
- }
-
- /** For external use only to generate V4 & tree separately. */
- public byte[] produceV4Signature(DataSource dataSource, OutputStream sigOutput)
- throws SignatureException {
- if (sigOutput == null) {
- throw new SignatureException("Missing V4 output streams.");
- }
- try {
- ApkSigningBlockUtils.SignerConfig v4SignerConfig = createV4SignerConfig();
- Pair pair =
- V4SchemeSigner.generateV4Signature(dataSource, v4SignerConfig);
- pair.getFirst().writeTo(sigOutput);
- return pair.getSecond();
- } catch (InvalidKeyException | IOException | NoSuchAlgorithmException e) {
- throw new SignatureException("V4 signing failed", e);
- }
- }
-
- @Override
- public boolean isEligibleForSourceStamp() {
- return mSourceStampSignerConfig != null
- && (mV2SigningEnabled || mV3SigningEnabled || mV1SigningEnabled);
- }
-
- @Override
- public byte[] generateSourceStampCertificateDigest() throws SignatureException {
- if (mSourceStampSignerConfig.getCertificates().isEmpty()) {
- throw new SignatureException("No certificates configured for stamp");
- }
- try {
- return computeSha256DigestBytes(
- mSourceStampSignerConfig.getCertificates().get(0).getEncoded());
- } catch (CertificateEncodingException e) {
- throw new SignatureException("Failed to encode source stamp certificate", e);
- }
- }
-
- @Override
- public void close() {
- mClosed = true;
-
- mAddV1SignatureRequest = null;
- mInputJarManifestEntryDataRequest = null;
- mOutputAndroidManifestEntryDataRequest = null;
- mDebuggable = null;
- mOutputJarEntryDigestRequests.clear();
- mOutputJarEntryDigests.clear();
- mEmittedSignatureJarEntryData.clear();
- mOutputSignatureJarEntryDataRequests.clear();
-
- mAddSigningBlockRequest = null;
- }
-
- private void invalidateV1Signature() {
- if (mV1SigningEnabled) {
- mV1SignaturePending = true;
- }
- invalidateV2Signature();
- }
-
- private void invalidateV2Signature() {
- if (mV2SigningEnabled) {
- mV2SignaturePending = true;
- mAddSigningBlockRequest = null;
- }
- }
-
- private void invalidateV3Signature() {
- if (mV3SigningEnabled) {
- mV3SignaturePending = true;
- mAddSigningBlockRequest = null;
- }
- }
-
- private void checkNotClosed() {
- if (mClosed) {
- throw new IllegalStateException("Engine closed");
- }
- }
-
- private void checkV1SigningDoneIfEnabled() {
- if (!mV1SignaturePending) {
- return;
- }
-
- if (mAddV1SignatureRequest == null) {
- throw new IllegalStateException(
- "v1 signature (JAR signature) not yet generated. Skipped outputJarEntries()?");
- }
- if (!mAddV1SignatureRequest.isDone()) {
- throw new IllegalStateException(
- "v1 signature (JAR signature) addition requested by outputJarEntries() hasn't"
- + " been fulfilled");
- }
- for (Map.Entry expectedOutputEntry :
- mEmittedSignatureJarEntryData.entrySet()) {
- String entryName = expectedOutputEntry.getKey();
- byte[] expectedData = expectedOutputEntry.getValue();
- GetJarEntryDataRequest actualDataRequest =
- mOutputSignatureJarEntryDataRequests.get(entryName);
- if (actualDataRequest == null) {
- throw new IllegalStateException(
- "APK entry "
- + entryName
- + " not yet output despite this having been"
- + " requested");
- } else if (!actualDataRequest.isDone()) {
- throw new IllegalStateException(
- "Still waiting to inspect output APK's " + entryName);
- }
- byte[] actualData = actualDataRequest.getData();
- if (!Arrays.equals(expectedData, actualData)) {
- throw new IllegalStateException(
- "Output APK entry " + entryName + " data differs from what was requested");
- }
- }
- mV1SignaturePending = false;
- }
-
- private void checkSigningBlockDoneIfEnabled() {
- if (!mV2SignaturePending && !mV3SignaturePending) {
- return;
- }
- if (mAddSigningBlockRequest == null) {
- throw new IllegalStateException(
- "Signed APK Signing BLock not yet generated. Skipped outputZipSections()?");
- }
- if (!mAddSigningBlockRequest.isDone()) {
- throw new IllegalStateException(
- "APK Signing Block addition of signature(s) requested by"
- + " outputZipSections() hasn't been fulfilled yet");
- }
- mAddSigningBlockRequest = null;
- mV2SignaturePending = false;
- mV3SignaturePending = false;
- }
-
- private void checkOutputApkNotDebuggableIfDebuggableMustBeRejected() throws SignatureException {
- if (mDebuggableApkPermitted) {
- return;
- }
-
- try {
- if (isOutputApkDebuggable()) {
- throw new SignatureException(
- "APK is debuggable (see android:debuggable attribute) and this engine is"
- + " configured to refuse to sign debuggable APKs");
- }
- } catch (ApkFormatException e) {
- throw new SignatureException("Failed to determine whether the APK is debuggable", e);
- }
- }
-
- /**
- * Returns whether the output APK is debuggable according to its {@code android:debuggable}
- * declaration.
- */
- private boolean isOutputApkDebuggable() throws ApkFormatException {
- if (mDebuggable != null) {
- return mDebuggable;
- }
-
- if (mOutputAndroidManifestEntryDataRequest == null) {
- throw new IllegalStateException(
- "Cannot determine debuggable status of output APK because "
- + ApkUtils.ANDROID_MANIFEST_ZIP_ENTRY_NAME
- + " entry contents have not yet been requested");
- }
-
- if (!mOutputAndroidManifestEntryDataRequest.isDone()) {
- throw new IllegalStateException(
- "Still waiting to inspect output APK's "
- + mOutputAndroidManifestEntryDataRequest.getEntryName());
- }
- mDebuggable =
- ApkUtils.getDebuggableFromBinaryAndroidManifest(
- ByteBuffer.wrap(mOutputAndroidManifestEntryDataRequest.getData()));
- return mDebuggable;
- }
-
- private void forgetOutputApkDebuggableStatus() {
- mDebuggable = null;
- }
-
- /** Returns the output policy for the provided input JAR entry. */
- private InputJarEntryInstructions.OutputPolicy getInputJarEntryOutputPolicy(String entryName) {
- if (mSignatureExpectedOutputJarEntryNames.contains(entryName)) {
- return InputJarEntryInstructions.OutputPolicy.OUTPUT_BY_ENGINE;
- }
- if ((mOtherSignersSignaturesPreserved)
- || (V1SchemeSigner.isJarEntryDigestNeededInManifest(entryName))) {
- return InputJarEntryInstructions.OutputPolicy.OUTPUT;
- }
- return InputJarEntryInstructions.OutputPolicy.SKIP;
- }
-
- private static class OutputJarSignatureRequestImpl implements OutputJarSignatureRequest {
- private final List mAdditionalJarEntries;
- private volatile boolean mDone;
-
- private OutputJarSignatureRequestImpl(List additionalZipEntries) {
- mAdditionalJarEntries =
- Collections.unmodifiableList(new ArrayList<>(additionalZipEntries));
- }
-
- @Override
- public List getAdditionalJarEntries() {
- return mAdditionalJarEntries;
- }
-
- @Override
- public void done() {
- mDone = true;
- }
-
- private boolean isDone() {
- return mDone;
- }
- }
-
- @SuppressWarnings("deprecation")
- private static class OutputApkSigningBlockRequestImpl
- implements OutputApkSigningBlockRequest, OutputApkSigningBlockRequest2 {
- private final byte[] mApkSigningBlock;
- private final int mPaddingBeforeApkSigningBlock;
- private volatile boolean mDone;
-
- private OutputApkSigningBlockRequestImpl(byte[] apkSigingBlock, int paddingBefore) {
- mApkSigningBlock = apkSigingBlock.clone();
- mPaddingBeforeApkSigningBlock = paddingBefore;
- }
-
- @Override
- public byte[] getApkSigningBlock() {
- return mApkSigningBlock.clone();
- }
-
- @Override
- public void done() {
- mDone = true;
- }
-
- private boolean isDone() {
- return mDone;
- }
-
- @Override
- public int getPaddingSizeBeforeApkSigningBlock() {
- return mPaddingBeforeApkSigningBlock;
- }
- }
-
- /** JAR entry inspection request which obtain the entry's uncompressed data. */
- private static class GetJarEntryDataRequest implements InspectJarEntryRequest {
- private final String mEntryName;
- private final Object mLock = new Object();
-
- private boolean mDone;
- private DataSink mDataSink;
- private ByteArrayOutputStream mDataSinkBuf;
-
- private GetJarEntryDataRequest(String entryName) {
- mEntryName = entryName;
- }
-
- @Override
- public String getEntryName() {
- return mEntryName;
- }
-
- @Override
- public DataSink getDataSink() {
- synchronized (mLock) {
- checkNotDone();
- if (mDataSinkBuf == null) {
- mDataSinkBuf = new ByteArrayOutputStream();
- }
- if (mDataSink == null) {
- mDataSink = DataSinks.asDataSink(mDataSinkBuf);
- }
- return mDataSink;
- }
- }
-
- @Override
- public void done() {
- synchronized (mLock) {
- if (mDone) {
- return;
- }
- mDone = true;
- }
- }
-
- private boolean isDone() {
- synchronized (mLock) {
- return mDone;
- }
- }
-
- private void checkNotDone() throws IllegalStateException {
- synchronized (mLock) {
- if (mDone) {
- throw new IllegalStateException("Already done");
- }
- }
- }
-
- private byte[] getData() {
- synchronized (mLock) {
- if (!mDone) {
- throw new IllegalStateException("Not yet done");
- }
- return (mDataSinkBuf != null) ? mDataSinkBuf.toByteArray() : new byte[0];
- }
- }
- }
-
- /** JAR entry inspection request which obtains the digest of the entry's uncompressed data. */
- private static class GetJarEntryDataDigestRequest implements InspectJarEntryRequest {
- private final String mEntryName;
- private final String mJcaDigestAlgorithm;
- private final Object mLock = new Object();
-
- private boolean mDone;
- private DataSink mDataSink;
- private MessageDigest mMessageDigest;
- private byte[] mDigest;
-
- private GetJarEntryDataDigestRequest(String entryName, String jcaDigestAlgorithm) {
- mEntryName = entryName;
- mJcaDigestAlgorithm = jcaDigestAlgorithm;
- }
-
- @Override
- public String getEntryName() {
- return mEntryName;
- }
-
- @Override
- public DataSink getDataSink() {
- synchronized (mLock) {
- checkNotDone();
- if (mDataSink == null) {
- mDataSink = DataSinks.asDataSink(getMessageDigest());
- }
- return mDataSink;
- }
- }
-
- private MessageDigest getMessageDigest() {
- synchronized (mLock) {
- if (mMessageDigest == null) {
- try {
- mMessageDigest = MessageDigest.getInstance(mJcaDigestAlgorithm);
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException(
- mJcaDigestAlgorithm + " MessageDigest not available", e);
- }
- }
- return mMessageDigest;
- }
- }
-
- @Override
- public void done() {
- synchronized (mLock) {
- if (mDone) {
- return;
- }
- mDone = true;
- mDigest = getMessageDigest().digest();
- mMessageDigest = null;
- mDataSink = null;
- }
- }
-
- private boolean isDone() {
- synchronized (mLock) {
- return mDone;
- }
- }
-
- private void checkNotDone() throws IllegalStateException {
- synchronized (mLock) {
- if (mDone) {
- throw new IllegalStateException("Already done");
- }
- }
- }
-
- private byte[] getDigest() {
- synchronized (mLock) {
- if (!mDone) {
- throw new IllegalStateException("Not yet done");
- }
- return mDigest.clone();
- }
- }
- }
-
- /** JAR entry inspection request which transparently satisfies multiple such requests. */
- private static class CompoundInspectJarEntryRequest implements InspectJarEntryRequest {
- private final String mEntryName;
- private final InspectJarEntryRequest[] mRequests;
- private final Object mLock = new Object();
-
- private DataSink mSink;
-
- private CompoundInspectJarEntryRequest(
- String entryName, InspectJarEntryRequest... requests) {
- mEntryName = entryName;
- mRequests = requests;
- }
-
- @Override
- public String getEntryName() {
- return mEntryName;
- }
-
- @Override
- public DataSink getDataSink() {
- synchronized (mLock) {
- if (mSink == null) {
- DataSink[] sinks = new DataSink[mRequests.length];
- for (int i = 0; i < sinks.length; i++) {
- sinks[i] = mRequests[i].getDataSink();
- }
- mSink = new TeeDataSink(sinks);
- }
- return mSink;
- }
- }
-
- @Override
- public void done() {
- for (InspectJarEntryRequest request : mRequests) {
- request.done();
- }
- }
- }
-
- /**
- * Configuration of a signer.
- *
- * Use {@link Builder} to obtain configuration instances.
- */
- public static class SignerConfig {
- private final String mName;
- private final PrivateKey mPrivateKey;
- private final List mCertificates;
- private final boolean mDeterministicDsaSigning;
-
- private SignerConfig(
- String name, PrivateKey privateKey, List certificates,
- boolean deterministicDsaSigning) {
- mName = name;
- mPrivateKey = privateKey;
- mCertificates = Collections.unmodifiableList(new ArrayList<>(certificates));
- mDeterministicDsaSigning = deterministicDsaSigning;
- }
-
- /** Returns the name of this signer. */
- public String getName() {
- return mName;
- }
-
- /** Returns the signing key of this signer. */
- public PrivateKey getPrivateKey() {
- return mPrivateKey;
- }
-
- /**
- * Returns the certificate(s) of this signer. The first certificate's public key corresponds
- * to this signer's private key.
- */
- public List getCertificates() {
- return mCertificates;
- }
-
- /**
- * If this signer is a DSA signer, whether or not the signing is done deterministically.
- */
- public boolean getDeterministicDsaSigning() {
- return mDeterministicDsaSigning;
- }
-
- /** Builder of {@link SignerConfig} instances. */
- public static class Builder {
- private final String mName;
- private final PrivateKey mPrivateKey;
- private final List mCertificates;
- private final boolean mDeterministicDsaSigning;
-
- /**
- * Constructs a new {@code Builder}.
- *
- * @param name signer's name. The name is reflected in the name of files comprising the
- * JAR signature of the APK.
- * @param privateKey signing key
- * @param certificates list of one or more X.509 certificates. The subject public key of
- * the first certificate must correspond to the {@code privateKey}.
- */
- public Builder(String name, PrivateKey privateKey, List certificates) {
- this(name, privateKey, certificates, false);
- }
-
- /**
- * Constructs a new {@code Builder}.
- *
- * @param name signer's name. The name is reflected in the name of files comprising the
- * JAR signature of the APK.
- * @param privateKey signing key
- * @param certificates list of one or more X.509 certificates. The subject public key of
- * the first certificate must correspond to the {@code privateKey}.
- * @param deterministicDsaSigning When signing using DSA, whether or not the
- * deterministic signing algorithm variant (RFC6979) should be used.
- */
- public Builder(String name, PrivateKey privateKey, List certificates,
- boolean deterministicDsaSigning) {
- if (name.isEmpty()) {
- throw new IllegalArgumentException("Empty name");
- }
- mName = name;
- mPrivateKey = privateKey;
- mCertificates = new ArrayList<>(certificates);
- mDeterministicDsaSigning = deterministicDsaSigning;
- }
-
- /**
- * Returns a new {@code SignerConfig} instance configured based on the configuration of
- * this builder.
- */
- public SignerConfig build() {
- return new SignerConfig(mName, mPrivateKey, mCertificates,
- mDeterministicDsaSigning);
- }
- }
- }
-
- /** Builder of {@link DefaultApkSignerEngine} instances. */
- public static class Builder {
- private List mSignerConfigs;
- private SignerConfig mStampSignerConfig;
- private SigningCertificateLineage mSourceStampSigningCertificateLineage;
- private final int mMinSdkVersion;
-
- private boolean mV1SigningEnabled = true;
- private boolean mV2SigningEnabled = true;
- private boolean mV3SigningEnabled = true;
- private boolean mVerityEnabled = false;
- private boolean mDebuggableApkPermitted = true;
- private boolean mOtherSignersSignaturesPreserved;
- private String mCreatedBy = "1.0 (Android)";
-
- private SigningCertificateLineage mSigningCertificateLineage;
-
- // APK Signature Scheme v3 only supports a single signing certificate, so to move to v3
- // signing by default, but not require prior clients to update to explicitly disable v3
- // signing for multiple signers, we modify the mV3SigningEnabled depending on the provided
- // inputs (multiple signers and mSigningCertificateLineage in particular). Maintain two
- // extra variables to record whether or not mV3SigningEnabled has been set directly by a
- // client and so should override the default behavior.
- private boolean mV3SigningExplicitlyDisabled = false;
- private boolean mV3SigningExplicitlyEnabled = false;
-
- /**
- * Constructs a new {@code Builder}.
- *
- * @param signerConfigs information about signers with which the APK will be signed. At
- * least one signer configuration must be provided.
- * @param minSdkVersion API Level of the oldest Android platform on which the APK is
- * supposed to be installed. See {@code minSdkVersion} attribute in the APK's {@code
- * AndroidManifest.xml}. The higher the version, the stronger signing features will be
- * enabled.
- */
- public Builder(List signerConfigs, int minSdkVersion) {
- if (signerConfigs.isEmpty()) {
- throw new IllegalArgumentException("At least one signer config must be provided");
- }
- if (signerConfigs.size() > 1) {
- // APK Signature Scheme v3 only supports single signer, unless a
- // SigningCertificateLineage is provided, in which case this will be reset to true,
- // since we don't yet have a v4 scheme about which to worry
- mV3SigningEnabled = false;
- }
- mSignerConfigs = new ArrayList<>(signerConfigs);
- mMinSdkVersion = minSdkVersion;
- }
-
- /**
- * Returns a new {@code DefaultApkSignerEngine} instance configured based on the
- * configuration of this builder.
- */
- public DefaultApkSignerEngine build() throws InvalidKeyException {
-
- if (mV3SigningExplicitlyDisabled && mV3SigningExplicitlyEnabled) {
- throw new IllegalStateException(
- "Builder configured to both enable and disable APK "
- + "Signature Scheme v3 signing");
- }
- if (mV3SigningExplicitlyDisabled) {
- mV3SigningEnabled = false;
- } else if (mV3SigningExplicitlyEnabled) {
- mV3SigningEnabled = true;
- }
-
- // make sure our signers are appropriately setup
- if (mSigningCertificateLineage != null) {
- try {
- mSignerConfigs = mSigningCertificateLineage.sortSignerConfigs(mSignerConfigs);
- if (!mV3SigningEnabled && mSignerConfigs.size() > 1) {
-
- // this is a strange situation: we've provided a valid rotation history, but
- // are only signing with v1/v2. blow up, since we don't know for sure with
- // which signer the user intended to sign
- throw new IllegalStateException(
- "Provided multiple signers which are part of the"
- + " SigningCertificateLineage, but not signing with APK"
- + " Signature Scheme v3");
- }
- } catch (IllegalArgumentException e) {
- throw new IllegalStateException(
- "Provided signer configs do not match the "
- + "provided SigningCertificateLineage",
- e);
- }
- } else if (mV3SigningEnabled && mSignerConfigs.size() > 1) {
- throw new IllegalStateException(
- "Multiple signing certificates provided for use with APK Signature Scheme"
- + " v3 without an accompanying SigningCertificateLineage");
- }
-
- return new DefaultApkSignerEngine(
- mSignerConfigs,
- mStampSignerConfig,
- mSourceStampSigningCertificateLineage,
- mMinSdkVersion,
- mV1SigningEnabled,
- mV2SigningEnabled,
- mV3SigningEnabled,
- mVerityEnabled,
- mDebuggableApkPermitted,
- mOtherSignersSignaturesPreserved,
- mCreatedBy,
- mSigningCertificateLineage);
- }
-
- /** Sets the signer configuration for the SourceStamp to be embedded in the APK. */
- public Builder setStampSignerConfig(SignerConfig stampSignerConfig) {
- mStampSignerConfig = stampSignerConfig;
- return this;
- }
-
- /**
- * Sets the source stamp {@link SigningCertificateLineage}. This structure provides proof of
- * signing certificate rotation for certificates previously used to sign source stamps.
- */
- public Builder setSourceStampSigningCertificateLineage(
- SigningCertificateLineage sourceStampSigningCertificateLineage) {
- mSourceStampSigningCertificateLineage = sourceStampSigningCertificateLineage;
- return this;
- }
-
- /**
- * Sets whether the APK should be signed using JAR signing (aka v1 signature scheme).
- *
- * By default, the APK will be signed using this scheme.
- */
- public Builder setV1SigningEnabled(boolean enabled) {
- mV1SigningEnabled = enabled;
- return this;
- }
-
- /**
- * Sets whether the APK should be signed using APK Signature Scheme v2 (aka v2 signature
- * scheme).
- *
- *
By default, the APK will be signed using this scheme.
- */
- public Builder setV2SigningEnabled(boolean enabled) {
- mV2SigningEnabled = enabled;
- return this;
- }
-
- /**
- * Sets whether the APK should be signed using APK Signature Scheme v3 (aka v3 signature
- * scheme).
- *
- *
By default, the APK will be signed using this scheme.
- */
- public Builder setV3SigningEnabled(boolean enabled) {
- mV3SigningEnabled = enabled;
- if (enabled) {
- mV3SigningExplicitlyEnabled = true;
- } else {
- mV3SigningExplicitlyDisabled = true;
- }
- return this;
- }
-
- /**
- * Sets whether the APK should be signed using the verity signature algorithm in the v2 and
- * v3 signature blocks.
- *
- *
By default, the APK will be signed using the verity signature algorithm for the v2 and
- * v3 signature schemes.
- */
- public Builder setVerityEnabled(boolean enabled) {
- mVerityEnabled = enabled;
- return this;
- }
-
- /**
- * Sets whether the APK should be signed even if it is marked as debuggable ({@code
- * android:debuggable="true"} in its {@code AndroidManifest.xml}). For backward
- * compatibility reasons, the default value of this setting is {@code true}.
- *
- *
It is dangerous to sign debuggable APKs with production/release keys because Android
- * platform loosens security checks for such APKs. For example, arbitrary unauthorized code
- * may be executed in the context of such an app by anybody with ADB shell access.
- */
- public Builder setDebuggableApkPermitted(boolean permitted) {
- mDebuggableApkPermitted = permitted;
- return this;
- }
-
- /**
- * Sets whether signatures produced by signers other than the ones configured in this engine
- * should be copied from the input APK to the output APK.
- *
- *
By default, signatures of other signers are omitted from the output APK.
- */
- public Builder setOtherSignersSignaturesPreserved(boolean preserved) {
- mOtherSignersSignaturesPreserved = preserved;
- return this;
- }
-
- /** Sets the value of the {@code Created-By} field in JAR signature files. */
- public Builder setCreatedBy(String createdBy) {
- if (createdBy == null) {
- throw new NullPointerException();
- }
- mCreatedBy = createdBy;
- return this;
- }
-
- /**
- * Sets the {@link SigningCertificateLineage} to use with the v3 signature scheme. This
- * structure provides proof of signing certificate rotation linking {@link SignerConfig}
- * objects to previous ones.
- */
- public Builder setSigningCertificateLineage(
- SigningCertificateLineage signingCertificateLineage) {
- if (signingCertificateLineage != null) {
- mV3SigningEnabled = true;
- mSigningCertificateLineage = signingCertificateLineage;
- }
- return this;
- }
- }
-}
diff --git a/app/src/main/java/com/android/apksig/Hints.java b/app/src/main/java/com/android/apksig/Hints.java
deleted file mode 100644
index 4070fa231a..0000000000
--- a/app/src/main/java/com/android/apksig/Hints.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.apksig;
-import java.io.IOException;
-import java.io.DataOutputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public final class Hints {
- /**
- * Name of hint pattern asset file in APK.
- */
- public static final String PIN_HINT_ASSET_ZIP_ENTRY_NAME = "assets/com.android.hints.pins.txt";
-
- /**
- * Name of hint byte range data file in APK. Keep in sync with PinnerService.java.
- */
- public static final String PIN_BYTE_RANGE_ZIP_ENTRY_NAME = "pinlist.meta";
-
- private static int clampToInt(long value) {
- return (int) Math.max(0, Math.min(value, Integer.MAX_VALUE));
- }
-
- public static final class ByteRange {
- final long start;
- final long end;
-
- public ByteRange(long start, long end) {
- this.start = start;
- this.end = end;
- }
- }
-
- public static final class PatternWithRange {
- final Pattern pattern;
- final long offset;
- final long size;
-
- public PatternWithRange(String pattern) {
- this.pattern = Pattern.compile(pattern);
- this.offset= 0;
- this.size = Long.MAX_VALUE;
- }
-
- public PatternWithRange(String pattern, long offset, long size) {
- this.pattern = Pattern.compile(pattern);
- this.offset = offset;
- this.size = size;
- }
-
- public Matcher matcher(CharSequence input) {
- return this.pattern.matcher(input);
- }
-
- public ByteRange ClampToAbsoluteByteRange(ByteRange rangeIn) {
- if (rangeIn.end - rangeIn.start < this.offset) {
- return null;
- }
- long rangeOutStart = rangeIn.start + this.offset;
- long rangeOutSize = Math.min(rangeIn.end - rangeOutStart,
- this.size);
- return new ByteRange(rangeOutStart,
- rangeOutStart + rangeOutSize);
- }
- }
-
- /**
- * Create a blob of bytes that PinnerService understands as a
- * sequence of byte ranges to pin.
- */
- public static byte[] encodeByteRangeList(List pinByteRanges) {
- ByteArrayOutputStream bos = new ByteArrayOutputStream(pinByteRanges.size() * 8);
- DataOutputStream out = new DataOutputStream(bos);
- try {
- for (ByteRange pinByteRange : pinByteRanges) {
- out.writeInt(clampToInt(pinByteRange.start));
- out.writeInt(clampToInt(pinByteRange.end - pinByteRange.start));
- }
- } catch (IOException ex) {
- throw new AssertionError("impossible", ex);
- }
- return bos.toByteArray();
- }
-
- public static ArrayList parsePinPatterns(byte[] patternBlob) {
- ArrayList pinPatterns = new ArrayList<>();
- try {
- for (String rawLine : new String(patternBlob, "UTF-8").split("\n")) {
- String line = rawLine.replaceFirst("#.*", ""); // # starts a comment
- String[] fields = line.split(" ");
- if (fields.length == 1) {
- pinPatterns.add(new PatternWithRange(fields[0]));
- } else if (fields.length == 3) {
- long start = Long.parseLong(fields[1]);
- long end = Long.parseLong(fields[2]);
- pinPatterns.add(new PatternWithRange(fields[0], start, end - start));
- } else {
- throw new AssertionError("bad pin pattern line " + line);
- }
- }
- } catch (UnsupportedEncodingException ex) {
- throw new RuntimeException("UTF-8 must be supported", ex);
- }
- return pinPatterns;
- }
-}
diff --git a/app/src/main/java/com/android/apksig/SigningCertificateLineage.java b/app/src/main/java/com/android/apksig/SigningCertificateLineage.java
deleted file mode 100644
index 767a1f6202..0000000000
--- a/app/src/main/java/com/android/apksig/SigningCertificateLineage.java
+++ /dev/null
@@ -1,1076 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.apksig;
-
-import static com.android.apksig.internal.apk.ApkSigningBlockUtils.getLengthPrefixedSlice;
-
-import com.android.apksig.apk.ApkFormatException;
-import com.android.apksig.apk.ApkUtils;
-import com.android.apksig.internal.apk.ApkSigningBlockUtils;
-import com.android.apksig.internal.apk.SignatureAlgorithm;
-import com.android.apksig.internal.apk.SignatureInfo;
-import com.android.apksig.internal.apk.v3.V3SchemeConstants;
-import com.android.apksig.internal.apk.v3.V3SchemeSigner;
-import com.android.apksig.internal.apk.v3.V3SigningCertificateLineage;
-import com.android.apksig.internal.apk.v3.V3SigningCertificateLineage.SigningCertificateNode;
-import com.android.apksig.internal.util.AndroidSdkVersion;
-import com.android.apksig.internal.util.ByteBufferUtils;
-import com.android.apksig.internal.util.Pair;
-import com.android.apksig.internal.util.RandomAccessFileDataSink;
-import com.android.apksig.util.DataSink;
-import com.android.apksig.util.DataSource;
-import com.android.apksig.util.DataSources;
-import com.android.apksig.zip.ZipFormatException;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SignatureException;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * APK Signer Lineage.
- *
- * The signer lineage contains a history of signing certificates with each ancestor attesting to
- * the validity of its descendant. Each additional descendant represents a new identity that can be
- * used to sign an APK, and each generation has accompanying attributes which represent how the
- * APK would like to view the older signing certificates, specifically how they should be trusted in
- * certain situations.
- *
- *
Its primary use is to enable APK Signing Certificate Rotation. The Android platform verifies
- * the APK Signer Lineage, and if the current signing certificate for the APK is in the Signer
- * Lineage, and the Lineage contains the certificate the platform associates with the APK, it will
- * allow upgrades to the new certificate.
- *
- * @see Application Signing
- */
-public class SigningCertificateLineage {
-
- public final static int MAGIC = 0x3eff39d1;
-
- private final static int FIRST_VERSION = 1;
-
- private static final int CURRENT_VERSION = FIRST_VERSION;
-
- /** accept data from already installed pkg with this cert */
- private static final int PAST_CERT_INSTALLED_DATA = 1;
-
- /** accept sharedUserId with pkg with this cert */
- private static final int PAST_CERT_SHARED_USER_ID = 2;
-
- /** grant SIGNATURE permissions to pkgs with this cert */
- private static final int PAST_CERT_PERMISSION = 4;
-
- /**
- * Enable updates back to this certificate. WARNING: this effectively removes any benefit of
- * signing certificate changes, since a compromised key could retake control of an app even
- * after change, and should only be used if there is a problem encountered when trying to ditch
- * an older cert.
- */
- private static final int PAST_CERT_ROLLBACK = 8;
-
- /**
- * Preserve authenticator module-based access in AccountManager gated by signing certificate.
- */
- private static final int PAST_CERT_AUTH = 16;
-
- private final int mMinSdkVersion;
-
- /**
- * The signing lineage is just a list of nodes, with the first being the original signing
- * certificate and the most recent being the one with which the APK is to actually be signed.
- */
- private final List mSigningLineage;
-
- private SigningCertificateLineage(int minSdkVersion, List list) {
- mMinSdkVersion = minSdkVersion;
- mSigningLineage = list;
- }
-
- private static SigningCertificateLineage createSigningLineage(
- int minSdkVersion, SignerConfig parent, SignerCapabilities parentCapabilities,
- SignerConfig child, SignerCapabilities childCapabilities)
- throws CertificateEncodingException, InvalidKeyException, NoSuchAlgorithmException,
- SignatureException {
- SigningCertificateLineage signingCertificateLineage =
- new SigningCertificateLineage(minSdkVersion, new ArrayList<>());
- signingCertificateLineage =
- signingCertificateLineage.spawnFirstDescendant(parent, parentCapabilities);
- return signingCertificateLineage.spawnDescendant(parent, child, childCapabilities);
- }
-
- public static SigningCertificateLineage readFromFile(File file)
- throws IOException {
- if (file == null) {
- throw new NullPointerException("file == null");
- }
- RandomAccessFile inputFile = new RandomAccessFile(file, "r");
- return readFromDataSource(DataSources.asDataSource(inputFile));
- }
-
- public static SigningCertificateLineage readFromDataSource(DataSource dataSource)
- throws IOException {
- if (dataSource == null) {
- throw new NullPointerException("dataSource == null");
- }
- ByteBuffer inBuff = dataSource.getByteBuffer(0, (int) dataSource.size());
- inBuff.order(ByteOrder.LITTLE_ENDIAN);
- return read(inBuff);
- }
-
- /**
- * Extracts a Signing Certificate Lineage from a v3 signer proof-of-rotation attribute.
- *
- *
- * this may not give a complete representation of an APK's signing certificate history,
- * since the APK may have multiple signers corresponding to different platform versions.
- * Use readFromApkFile to handle this case.
- *
- * @param attrValue
- */
- public static SigningCertificateLineage readFromV3AttributeValue(byte[] attrValue)
- throws IOException {
- List parsedLineage =
- V3SigningCertificateLineage.readSigningCertificateLineage(ByteBuffer.wrap(
- attrValue).order(ByteOrder.LITTLE_ENDIAN));
- int minSdkVersion = calculateMinSdkVersion(parsedLineage);
- return new SigningCertificateLineage(minSdkVersion, parsedLineage);
- }
-
- /**
- * Extracts a Signing Certificate Lineage from the proof-of-rotation attribute in the V3
- * signature block of the provided APK File.
- *
- * @throws IllegalArgumentException if the provided APK does not contain a V3 signature block,
- * or if the V3 signature block does not contain a valid lineage.
- */
- public static SigningCertificateLineage readFromApkFile(File apkFile)
- throws IOException, ApkFormatException {
- try (RandomAccessFile f = new RandomAccessFile(apkFile, "r")) {
- DataSource apk = DataSources.asDataSource(f, 0, f.length());
- return readFromApkDataSource(apk);
- }
- }
-
- /**
- * Extracts a Signing Certificate Lineage from the proof-of-rotation attribute in the V3
- * signature block of the provided APK DataSource.
- *
- * @throws IllegalArgumentException if the provided APK does not contain a V3 signature block,
- * or if the V3 signature block does not contain a valid lineage.
- */
- public static SigningCertificateLineage readFromApkDataSource(DataSource apk)
- throws IOException, ApkFormatException {
- SignatureInfo signatureInfo;
- try {
- ApkUtils.ZipSections zipSections = ApkUtils.findZipSections(apk);
- ApkSigningBlockUtils.Result result = new ApkSigningBlockUtils.Result(
- ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3);
- signatureInfo =
- ApkSigningBlockUtils.findSignature(apk, zipSections,
- V3SchemeConstants.APK_SIGNATURE_SCHEME_V3_BLOCK_ID, result);
- } catch (ZipFormatException e) {
- throw new ApkFormatException(e.getMessage());
- } catch (ApkSigningBlockUtils.SignatureNotFoundException e) {
- throw new IllegalArgumentException(
- "The provided APK does not contain a valid V3 signature block.");
- }
-
- // FORMAT:
- // * length-prefixed sequence of length-prefixed signers:
- // * length-prefixed signed data
- // * minSDK
- // * maxSDK
- // * length-prefixed sequence of length-prefixed signatures
- // * length-prefixed public key
- ByteBuffer signers = getLengthPrefixedSlice(signatureInfo.signatureBlock);
- List lineages = new ArrayList<>(1);
- while (signers.hasRemaining()) {
- ByteBuffer signer = getLengthPrefixedSlice(signers);
- ByteBuffer signedData = getLengthPrefixedSlice(signer);
- try {
- SigningCertificateLineage lineage = readFromSignedData(signedData);
- lineages.add(lineage);
- } catch (IllegalArgumentException ignored) {
- // The current signer block does not contain a valid lineage, but it is possible
- // another block will.
- }
- }
- SigningCertificateLineage result;
- if (lineages.isEmpty()) {
- throw new IllegalArgumentException(
- "The provided APK does not contain a valid lineage.");
- } else if (lineages.size() > 1) {
- result = consolidateLineages(lineages);
- } else {
- result = lineages.get(0);
- }
- return result;
- }
-
- /**
- * Extracts a Signing Certificate Lineage from the proof-of-rotation attribute in the provided
- * signed data portion of a signer in a V3 signature block.
- *
- * @throws IllegalArgumentException if the provided signed data does not contain a valid
- * lineage.
- */
- public static SigningCertificateLineage readFromSignedData(ByteBuffer signedData)
- throws IOException, ApkFormatException {
- // FORMAT:
- // * length-prefixed sequence of length-prefixed digests:
- // * length-prefixed sequence of certificates:
- // * length-prefixed bytes: X.509 certificate (ASN.1 DER encoded).
- // * uint-32: minSdkVersion
- // * uint-32: maxSdkVersion
- // * length-prefixed sequence of length-prefixed additional attributes:
- // * uint32: ID
- // * (length - 4) bytes: value
- // * uint32: Proof-of-rotation ID: 0x3ba06f8c
- // * length-prefixed proof-of-rotation structure
- // consume the digests through the maxSdkVersion to reach the lineage in the attributes
- getLengthPrefixedSlice(signedData);
- getLengthPrefixedSlice(signedData);
- signedData.getInt();
- signedData.getInt();
- // iterate over the additional attributes adding any lineages to the List
- ByteBuffer additionalAttributes = getLengthPrefixedSlice(signedData);
- List lineages = new ArrayList<>(1);
- while (additionalAttributes.hasRemaining()) {
- ByteBuffer attribute = getLengthPrefixedSlice(additionalAttributes);
- int id = attribute.getInt();
- if (id == V3SchemeConstants.PROOF_OF_ROTATION_ATTR_ID) {
- byte[] value = ByteBufferUtils.toByteArray(attribute);
- SigningCertificateLineage lineage = readFromV3AttributeValue(value);
- lineages.add(lineage);
- }
- }
- SigningCertificateLineage result;
- // There should only be a single attribute with the lineage, but if there are multiple then
- // attempt to consolidate the lineages.
- if (lineages.isEmpty()) {
- throw new IllegalArgumentException("The signed data does not contain a valid lineage.");
- } else if (lineages.size() > 1) {
- result = consolidateLineages(lineages);
- } else {
- result = lineages.get(0);
- }
- return result;
- }
-
- public void writeToFile(File file) throws IOException {
- if (file == null) {
- throw new NullPointerException("file == null");
- }
- RandomAccessFile outputFile = new RandomAccessFile(file, "rw");
- writeToDataSink(new RandomAccessFileDataSink(outputFile));
- }
-
- public void writeToDataSink(DataSink dataSink) throws IOException {
- if (dataSink == null) {
- throw new NullPointerException("dataSink == null");
- }
- dataSink.consume(write());
- }
-
- /**
- * Add a new signing certificate to the lineage. This effectively creates a signing certificate
- * rotation event, forcing APKs which include this lineage to be signed by the new signer. The
- * flags associated with the new signer are set to a default value.
- *
- * @param parent current signing certificate of the containing APK
- * @param child new signing certificate which will sign the APK contents
- */
- public SigningCertificateLineage spawnDescendant(SignerConfig parent, SignerConfig child)
- throws CertificateEncodingException, InvalidKeyException, NoSuchAlgorithmException,
- SignatureException {
- if (parent == null || child == null) {
- throw new NullPointerException("can't add new descendant to lineage with null inputs");
- }
- SignerCapabilities signerCapabilities = new SignerCapabilities.Builder().build();
- return spawnDescendant(parent, child, signerCapabilities);
- }
-
- /**
- * Add a new signing certificate to the lineage. This effectively creates a signing certificate
- * rotation event, forcing APKs which include this lineage to be signed by the new signer.
- *
- * @param parent current signing certificate of the containing APK
- * @param child new signing certificate which will sign the APK contents
- * @param childCapabilities flags
- */
- public SigningCertificateLineage spawnDescendant(
- SignerConfig parent, SignerConfig child, SignerCapabilities childCapabilities)
- throws CertificateEncodingException, InvalidKeyException,
- NoSuchAlgorithmException, SignatureException {
- if (parent == null) {
- throw new NullPointerException("parent == null");
- }
- if (child == null) {
- throw new NullPointerException("child == null");
- }
- if (childCapabilities == null) {
- throw new NullPointerException("childCapabilities == null");
- }
- if (mSigningLineage.isEmpty()) {
- throw new IllegalArgumentException("Cannot spawn descendant signing certificate on an"
- + " empty SigningCertificateLineage: no parent node");
- }
-
- // make sure that the parent matches our newest generation (leaf node/sink)
- SigningCertificateNode currentGeneration = mSigningLineage.get(mSigningLineage.size() - 1);
- if (!Arrays.equals(currentGeneration.signingCert.getEncoded(),
- parent.getCertificate().getEncoded())) {
- throw new IllegalArgumentException("SignerConfig Certificate containing private key"
- + " to sign the new SigningCertificateLineage record does not match the"
- + " existing most recent record");
- }
-
- // create data to be signed, including the algorithm we're going to use
- SignatureAlgorithm signatureAlgorithm = getSignatureAlgorithm(parent);
- ByteBuffer prefixedSignedData = ByteBuffer.wrap(
- V3SigningCertificateLineage.encodeSignedData(
- child.getCertificate(), signatureAlgorithm.getId()));
- prefixedSignedData.position(4);
- ByteBuffer signedDataBuffer = ByteBuffer.allocate(prefixedSignedData.remaining());
- signedDataBuffer.put(prefixedSignedData);
- byte[] signedData = signedDataBuffer.array();
-
- // create SignerConfig to do the signing
- List certificates = new ArrayList<>(1);
- certificates.add(parent.getCertificate());
- ApkSigningBlockUtils.SignerConfig newSignerConfig =
- new ApkSigningBlockUtils.SignerConfig();
- newSignerConfig.privateKey = parent.getPrivateKey();
- newSignerConfig.certificates = certificates;
- newSignerConfig.signatureAlgorithms = Collections.singletonList(signatureAlgorithm);
-
- // sign it
- List> signatures =
- ApkSigningBlockUtils.generateSignaturesOverData(newSignerConfig, signedData);
-
- // finally, add it to our lineage
- SignatureAlgorithm sigAlgorithm = SignatureAlgorithm.findById(signatures.get(0).getFirst());
- byte[] signature = signatures.get(0).getSecond();
- currentGeneration.sigAlgorithm = sigAlgorithm;
- SigningCertificateNode childNode =
- new SigningCertificateNode(
- child.getCertificate(), sigAlgorithm, null,
- signature, childCapabilities.getFlags());
- List lineageCopy = new ArrayList<>(mSigningLineage);
- lineageCopy.add(childNode);
- return new SigningCertificateLineage(mMinSdkVersion, lineageCopy);
- }
-
- /**
- * The number of signing certificates in the lineage, including the current signer, which means
- * this value can also be used to V2determine the number of signing certificate rotations by
- * subtracting 1.
- */
- public int size() {
- return mSigningLineage.size();
- }
-
- private SignatureAlgorithm getSignatureAlgorithm(SignerConfig parent)
- throws InvalidKeyException {
- PublicKey publicKey = parent.getCertificate().getPublicKey();
-
- // TODO switch to one signature algorithm selection, or add support for multiple algorithms
- List algorithms = V3SchemeSigner.getSuggestedSignatureAlgorithms(
- publicKey, mMinSdkVersion, false /* verityEnabled */,
- false /* deterministicDsaSigning */);
- return algorithms.get(0);
- }
-
- private SigningCertificateLineage spawnFirstDescendant(
- SignerConfig parent, SignerCapabilities signerCapabilities) {
- if (!mSigningLineage.isEmpty()) {
- throw new IllegalStateException("SigningCertificateLineage already has its first node");
- }
-
- // check to make sure that the public key for the first node is acceptable for our minSdk
- try {
- getSignatureAlgorithm(parent);
- } catch (InvalidKeyException e) {
- throw new IllegalArgumentException("Algorithm associated with first signing certificate"
- + " invalid on desired platform versions", e);
- }
-
- // create "fake" signed data (there will be no signature over it, since there is no parent
- SigningCertificateNode firstNode = new SigningCertificateNode(
- parent.getCertificate(), null, null, new byte[0], signerCapabilities.getFlags());
- return new SigningCertificateLineage(mMinSdkVersion, Collections.singletonList(firstNode));
- }
-
- private static SigningCertificateLineage read(ByteBuffer inputByteBuffer)
- throws IOException {
- ApkSigningBlockUtils.checkByteOrderLittleEndian(inputByteBuffer);
- if (inputByteBuffer.remaining() < 8) {
- throw new IllegalArgumentException(
- "Improper SigningCertificateLineage format: insufficient data for header.");
- }
-
- if (inputByteBuffer.getInt() != MAGIC) {
- throw new IllegalArgumentException(
- "Improper SigningCertificateLineage format: MAGIC header mismatch.");
- }
- return read(inputByteBuffer, inputByteBuffer.getInt());
- }
-
- private static SigningCertificateLineage read(ByteBuffer inputByteBuffer, int version)
- throws IOException {
- switch (version) {
- case FIRST_VERSION:
- try {
- List nodes =
- V3SigningCertificateLineage.readSigningCertificateLineage(
- getLengthPrefixedSlice(inputByteBuffer));
- int minSdkVersion = calculateMinSdkVersion(nodes);
- return new SigningCertificateLineage(minSdkVersion, nodes);
- } catch (ApkFormatException e) {
- // unable to get a proper length-prefixed lineage slice
- throw new IOException("Unable to read list of signing certificate nodes in "
- + "SigningCertificateLineage", e);
- }
- default:
- throw new IllegalArgumentException(
- "Improper SigningCertificateLineage format: unrecognized version.");
- }
- }
-
- private static int calculateMinSdkVersion(List nodes) {
- if (nodes == null) {
- throw new IllegalArgumentException("Can't calculate minimum SDK version of null nodes");
- }
- int minSdkVersion = AndroidSdkVersion.P; // lineage introduced in P
- for (SigningCertificateNode node : nodes) {
- if (node.sigAlgorithm != null) {
- int nodeMinSdkVersion = node.sigAlgorithm.getMinSdkVersion();
- if (nodeMinSdkVersion > minSdkVersion) {
- minSdkVersion = nodeMinSdkVersion;
- }
- }
- }
- return minSdkVersion;
- }
-
- private ByteBuffer write() {
- byte[] encodedLineage =
- V3SigningCertificateLineage.encodeSigningCertificateLineage(mSigningLineage);
- int payloadSize = 4 + 4 + 4 + encodedLineage.length;
- ByteBuffer result = ByteBuffer.allocate(payloadSize);
- result.order(ByteOrder.LITTLE_ENDIAN);
- result.putInt(MAGIC);
- result.putInt(CURRENT_VERSION);
- result.putInt(encodedLineage.length);
- result.put(encodedLineage);
- result.flip();
- return result;
- }
-
- public byte[] encodeSigningCertificateLineage() {
- return V3SigningCertificateLineage.encodeSigningCertificateLineage(mSigningLineage);
- }
-
- public List sortSignerConfigs(
- List signerConfigs) {
- if (signerConfigs == null) {
- throw new NullPointerException("signerConfigs == null");
- }
-
- // not the most elegant sort, but we expect signerConfigs to be quite small (1 or 2 signers
- // in most cases) and likely already sorted, so not worth the overhead of doing anything
- // fancier
- List sortedSignerConfigs =
- new ArrayList<>(signerConfigs.size());
- for (int i = 0; i < mSigningLineage.size(); i++) {
- for (int j = 0; j < signerConfigs.size(); j++) {
- DefaultApkSignerEngine.SignerConfig config = signerConfigs.get(j);
- if (mSigningLineage.get(i).signingCert.equals(config.getCertificates().get(0))) {
- sortedSignerConfigs.add(config);
- break;
- }
- }
- }
- if (sortedSignerConfigs.size() != signerConfigs.size()) {
- throw new IllegalArgumentException("SignerConfigs supplied which are not present in the"
- + " SigningCertificateLineage");
- }
- return sortedSignerConfigs;
- }
-
- /**
- * Returns the SignerCapabilities for the signer in the lineage that matches the provided
- * config.
- */
- public SignerCapabilities getSignerCapabilities(SignerConfig config) {
- if (config == null) {
- throw new NullPointerException("config == null");
- }
-
- X509Certificate cert = config.getCertificate();
- return getSignerCapabilities(cert);
- }
-
- /**
- * Returns the SignerCapabilities for the signer in the lineage that matches the provided
- * certificate.
- */
- public SignerCapabilities getSignerCapabilities(X509Certificate cert) {
- if (cert == null) {
- throw new NullPointerException("cert == null");
- }
-
- for (int i = 0; i < mSigningLineage.size(); i++) {
- SigningCertificateNode lineageNode = mSigningLineage.get(i);
- if (lineageNode.signingCert.equals(cert)) {
- int flags = lineageNode.flags;
- return new SignerCapabilities.Builder(flags).build();
- }
- }
-
- // the provided signer certificate was not found in the lineage
- throw new IllegalArgumentException("Certificate (" + cert.getSubjectDN()
- + ") not found in the SigningCertificateLineage");
- }
-
- /**
- * Updates the SignerCapabilities for the signer in the lineage that matches the provided
- * config. Only those capabilities that have been modified through the setXX methods will be
- * updated for the signer to prevent unset default values from being applied.
- */
- public void updateSignerCapabilities(SignerConfig config, SignerCapabilities capabilities) {
- if (config == null) {
- throw new NullPointerException("config == null");
- }
-
- X509Certificate cert = config.getCertificate();
- for (int i = 0; i < mSigningLineage.size(); i++) {
- SigningCertificateNode lineageNode = mSigningLineage.get(i);
- if (lineageNode.signingCert.equals(cert)) {
- int flags = lineageNode.flags;
- SignerCapabilities newCapabilities = new SignerCapabilities.Builder(
- flags).setCallerConfiguredCapabilities(capabilities).build();
- lineageNode.flags = newCapabilities.getFlags();
- return;
- }
- }
-
- // the provided signer config was not found in the lineage
- throw new IllegalArgumentException("Certificate (" + cert.getSubjectDN()
- + ") not found in the SigningCertificateLineage");
- }
-
- /**
- * Returns a list containing all of the certificates in the lineage.
- */
- public List getCertificatesInLineage() {
- List certs = new ArrayList<>();
- for (int i = 0; i < mSigningLineage.size(); i++) {
- X509Certificate cert = mSigningLineage.get(i).signingCert;
- certs.add(cert);
- }
- return certs;
- }
-
- /**
- * Returns {@code true} if the specified config is in the lineage.
- */
- public boolean isSignerInLineage(SignerConfig config) {
- if (config == null) {
- throw new NullPointerException("config == null");
- }
-
- X509Certificate cert = config.getCertificate();
- return isCertificateInLineage(cert);
- }
-
- /**
- * Returns {@code true} if the specified certificate is in the lineage.
- */
- public boolean isCertificateInLineage(X509Certificate cert) {
- if (cert == null) {
- throw new NullPointerException("cert == null");
- }
-
- for (int i = 0; i < mSigningLineage.size(); i++) {
- if (mSigningLineage.get(i).signingCert.equals(cert)) {
- return true;
- }
- }
- return false;
- }
-
- private static int calculateDefaultFlags() {
- return PAST_CERT_INSTALLED_DATA | PAST_CERT_PERMISSION
- | PAST_CERT_SHARED_USER_ID | PAST_CERT_AUTH;
- }
-
- /**
- * Returns a new SigingCertificateLineage which terminates at the node corresponding to the
- * given certificate. This is useful in the event of rotating to a new signing algorithm that
- * is only supported on some platform versions. It enables a v3 signature to be generated using
- * this signing certificate and the shortened proof-of-rotation record from this sub lineage in
- * conjunction with the appropriate SDK version values.
- *
- * @param x509Certificate the signing certificate for which to search
- * @return A new SigningCertificateLineage if the given certificate is present.
- *
- * @throws IllegalArgumentException if the provided certificate is not in the lineage.
- */
- public SigningCertificateLineage getSubLineage(X509Certificate x509Certificate) {
- if (x509Certificate == null) {
- throw new NullPointerException("x509Certificate == null");
- }
- for (int i = 0; i < mSigningLineage.size(); i++) {
- if (mSigningLineage.get(i).signingCert.equals(x509Certificate)) {
- return new SigningCertificateLineage(
- mMinSdkVersion, new ArrayList<>(mSigningLineage.subList(0, i + 1)));
- }
- }
-
- // looks like we didn't find the cert,
- throw new IllegalArgumentException("Certificate not found in SigningCertificateLineage");
- }
-
- /**
- * Consolidates all of the lineages found in an APK into one lineage, which is the longest one.
- * In so doing, it also checks that all of the smaller lineages are contained in the largest,
- * and that they properly cover the desired platform ranges.
- *
- * An APK may contain multiple lineages, one for each signer, which correspond to different
- * supported platform versions. In this event, the lineage(s) from the earlier platform
- * version(s) need to be present in the most recent (longest) one to make sure that when a
- * platform version changes.
- *
- * This does not verify that the largest lineage corresponds to the most recent supported
- * platform version. That check requires is performed during v3 verification.
- */
- public static SigningCertificateLineage consolidateLineages(
- List lineages) {
- if (lineages == null || lineages.isEmpty()) {
- return null;
- }
- int largestIndex = 0;
- int maxSize = 0;
-
- // determine the longest chain
- for (int i = 0; i < lineages.size(); i++) {
- int curSize = lineages.get(i).size();
- if (curSize > maxSize) {
- largestIndex = i;
- maxSize = curSize;
- }
- }
-
- List largestList = lineages.get(largestIndex).mSigningLineage;
- // make sure all other lineages fit into this one, with the same capabilities
- for (int i = 0; i < lineages.size(); i++) {
- if (i == largestIndex) {
- continue;
- }
- List underTest = lineages.get(i).mSigningLineage;
- if (!underTest.equals(largestList.subList(0, underTest.size()))) {
- throw new IllegalArgumentException("Inconsistent SigningCertificateLineages. "
- + "Not all lineages are subsets of each other.");
- }
- }
-
- // if we've made it this far, they all check out, so just return the largest
- return lineages.get(largestIndex);
- }
-
- /**
- * Representation of the capabilities the APK would like to grant to its old signing
- * certificates. The {@code SigningCertificateLineage} provides two conceptual data structures.
- * 1) proof of rotation - Evidence that other parties can trust an APK's current signing
- * certificate if they trust an older one in this lineage
- * 2) self-trust - certain capabilities may have been granted by an APK to other parties based
- * on its own signing certificate. When it changes its signing certificate it may want to
- * allow the other parties to retain those capabilities.
- * {@code SignerCapabilties} provides a representation of the second structure.
- *
- * Use {@link Builder} to obtain configuration instances.
- */
- public static class SignerCapabilities {
- private final int mFlags;
-
- private final int mCallerConfiguredFlags;
-
- private SignerCapabilities(int flags) {
- this(flags, 0);
- }
-
- private SignerCapabilities(int flags, int callerConfiguredFlags) {
- mFlags = flags;
- mCallerConfiguredFlags = callerConfiguredFlags;
- }
-
- private int getFlags() {
- return mFlags;
- }
-
- /**
- * Returns {@code true} if the capabilities of this object match those of the provided
- * object.
- */
- public boolean equals(SignerCapabilities other) {
- return this.mFlags == other.mFlags;
- }
-
- /**
- * Returns {@code true} if this object has the installed data capability.
- */
- public boolean hasInstalledData() {
- return (mFlags & PAST_CERT_INSTALLED_DATA) != 0;
- }
-
- /**
- * Returns {@code true} if this object has the shared UID capability.
- */
- public boolean hasSharedUid() {
- return (mFlags & PAST_CERT_SHARED_USER_ID) != 0;
- }
-
- /**
- * Returns {@code true} if this object has the permission capability.
- */
- public boolean hasPermission() {
- return (mFlags & PAST_CERT_PERMISSION) != 0;
- }
-
- /**
- * Returns {@code true} if this object has the rollback capability.
- */
- public boolean hasRollback() {
- return (mFlags & PAST_CERT_ROLLBACK) != 0;
- }
-
- /**
- * Returns {@code true} if this object has the auth capability.
- */
- public boolean hasAuth() {
- return (mFlags & PAST_CERT_AUTH) != 0;
- }
-
- /**
- * Builder of {@link SignerCapabilities} instances.
- */
- public static class Builder {
- private int mFlags;
-
- private int mCallerConfiguredFlags;
-
- /**
- * Constructs a new {@code Builder}.
- */
- public Builder() {
- mFlags = calculateDefaultFlags();
- }
-
- /**
- * Constructs a new {@code Builder} with the initial capabilities set to the provided
- * flags.
- */
- public Builder(int flags) {
- mFlags = flags;
- }
-
- /**
- * Set the {@code PAST_CERT_INSTALLED_DATA} flag in this capabilities object. This flag
- * is used by the platform to determine if installed data associated with previous
- * signing certificate should be trusted. In particular, this capability is required to
- * perform signing certificate rotation during an upgrade on-device. Without it, the
- * platform will not permit the app data from the old signing certificate to
- * propagate to the new version. Typically, this flag should be set to enable signing
- * certificate rotation, and may be unset later when the app developer is satisfied that
- * their install base is as migrated as it will be.
- */
- public Builder setInstalledData(boolean enabled) {
- mCallerConfiguredFlags |= PAST_CERT_INSTALLED_DATA;
- if (enabled) {
- mFlags |= PAST_CERT_INSTALLED_DATA;
- } else {
- mFlags &= ~PAST_CERT_INSTALLED_DATA;
- }
- return this;
- }
-
- /**
- * Set the {@code PAST_CERT_SHARED_USER_ID} flag in this capabilities object. This flag
- * is used by the platform to determine if this app is willing to be sharedUid with
- * other apps which are still signed with the associated signing certificate. This is
- * useful in situations where sharedUserId apps would like to change their signing
- * certificate, but can't guarantee the order of updates to those apps.
- */
- public Builder setSharedUid(boolean enabled) {
- mCallerConfiguredFlags |= PAST_CERT_SHARED_USER_ID;
- if (enabled) {
- mFlags |= PAST_CERT_SHARED_USER_ID;
- } else {
- mFlags &= ~PAST_CERT_SHARED_USER_ID;
- }
- return this;
- }
-
- /**
- * Set the {@code PAST_CERT_PERMISSION} flag in this capabilities object. This flag
- * is used by the platform to determine if this app is willing to grant SIGNATURE
- * permissions to apps signed with the associated signing certificate. Without this
- * capability, an application signed with the older certificate will not be granted the
- * SIGNATURE permissions defined by this app. In addition, if multiple apps define the
- * same SIGNATURE permission, the second one the platform sees will not be installable
- * if this capability is not set and the signing certificates differ.
- */
- public Builder setPermission(boolean enabled) {
- mCallerConfiguredFlags |= PAST_CERT_PERMISSION;
- if (enabled) {
- mFlags |= PAST_CERT_PERMISSION;
- } else {
- mFlags &= ~PAST_CERT_PERMISSION;
- }
- return this;
- }
-
- /**
- * Set the {@code PAST_CERT_ROLLBACK} flag in this capabilities object. This flag
- * is used by the platform to determine if this app is willing to upgrade to a new
- * version that is signed by one of its past signing certificates.
- *
- * WARNING: this effectively removes any benefit of signing certificate changes,
- * since a compromised key could retake control of an app even after change, and should
- * only be used if there is a problem encountered when trying to ditch an older cert
- *
- */
- public Builder setRollback(boolean enabled) {
- mCallerConfiguredFlags |= PAST_CERT_ROLLBACK;
- if (enabled) {
- mFlags |= PAST_CERT_ROLLBACK;
- } else {
- mFlags &= ~PAST_CERT_ROLLBACK;
- }
- return this;
- }
-
- /**
- * Set the {@code PAST_CERT_AUTH} flag in this capabilities object. This flag
- * is used by the platform to determine whether or not privileged access based on
- * authenticator module signing certificates should be granted.
- */
- public Builder setAuth(boolean enabled) {
- mCallerConfiguredFlags |= PAST_CERT_AUTH;
- if (enabled) {
- mFlags |= PAST_CERT_AUTH;
- } else {
- mFlags &= ~PAST_CERT_AUTH;
- }
- return this;
- }
-
- /**
- * Applies the capabilities that were explicitly set in the provided capabilities object
- * to this builder. Any values that were not set will not be applied to this builder
- * to prevent unintentinoally setting a capability back to a default value.
- */
- public Builder setCallerConfiguredCapabilities(SignerCapabilities capabilities) {
- // The mCallerConfiguredFlags should have a bit set for each capability that was
- // set by a caller. If a capability was explicitly set then the corresponding bit
- // in mCallerConfiguredFlags should be set. This allows the provided capabilities
- // to take effect for those set by the caller while those that were not set will
- // be cleared by the bitwise and and the initial value for the builder will remain.
- mFlags = (mFlags & ~capabilities.mCallerConfiguredFlags) |
- (capabilities.mFlags & capabilities.mCallerConfiguredFlags);
- return this;
- }
-
- /**
- * Returns a new {@code SignerConfig} instance configured based on the configuration of
- * this builder.
- */
- public SignerCapabilities build() {
- return new SignerCapabilities(mFlags, mCallerConfiguredFlags);
- }
- }
- }
-
- /**
- * Configuration of a signer. Used to add a new entry to the {@link SigningCertificateLineage}
- *
- *
Use {@link Builder} to obtain configuration instances.
- */
- public static class SignerConfig {
- private final PrivateKey mPrivateKey;
- private final X509Certificate mCertificate;
-
- private SignerConfig(
- PrivateKey privateKey,
- X509Certificate certificate) {
- mPrivateKey = privateKey;
- mCertificate = certificate;
- }
-
- /**
- * Returns the signing key of this signer.
- */
- public PrivateKey getPrivateKey() {
- return mPrivateKey;
- }
-
- /**
- * Returns the certificate(s) of this signer. The first certificate's public key corresponds
- * to this signer's private key.
- */
- public X509Certificate getCertificate() {
- return mCertificate;
- }
-
- /**
- * Builder of {@link SignerConfig} instances.
- */
- public static class Builder {
- private final PrivateKey mPrivateKey;
- private final X509Certificate mCertificate;
-
- /**
- * Constructs a new {@code Builder}.
- *
- * @param privateKey signing key
- * @param certificate the X.509 certificate with a subject public key of the
- * {@code privateKey}.
- */
- public Builder(
- PrivateKey privateKey,
- X509Certificate certificate) {
- mPrivateKey = privateKey;
- mCertificate = certificate;
- }
-
- /**
- * Returns a new {@code SignerConfig} instance configured based on the configuration of
- * this builder.
- */
- public SignerConfig build() {
- return new SignerConfig(
- mPrivateKey,
- mCertificate);
- }
- }
- }
-
- /**
- * Builder of {@link SigningCertificateLineage} instances.
- */
- public static class Builder {
- private final SignerConfig mOriginalSignerConfig;
- private final SignerConfig mNewSignerConfig;
- private SignerCapabilities mOriginalCapabilities;
- private SignerCapabilities mNewCapabilities;
- private int mMinSdkVersion;
- /**
- * Constructs a new {@code Builder}.
- *
- * @param originalSignerConfig first signer in this lineage, parent of the next
- * @param newSignerConfig new signer in the lineage; the new signing key that the APK will
- * use
- */
- public Builder(
- SignerConfig originalSignerConfig,
- SignerConfig newSignerConfig) {
- if (originalSignerConfig == null || newSignerConfig == null) {
- throw new NullPointerException("Can't pass null SignerConfigs when constructing a "
- + "new SigningCertificateLineage");
- }
- mOriginalSignerConfig = originalSignerConfig;
- mNewSignerConfig = newSignerConfig;
- }
-
- /**
- * Sets the minimum Android platform version (API Level) on which this lineage is expected
- * to validate. It is possible that newer signers in the lineage may not be recognized on
- * the given platform, but as long as an older signer is, the lineage can still be used to
- * sign an APK for the given platform.
- *
- * By default, this value is set to the value for the
- * P release, since this structure was created for that release, and will also be set to
- * that value if a smaller one is specified.
- */
- public Builder setMinSdkVersion(int minSdkVersion) {
- mMinSdkVersion = minSdkVersion;
- return this;
- }
-
- /**
- * Sets capabilities to give {@code mOriginalSignerConfig}. These capabilities allow an
- * older signing certificate to still be used in some situations on the platform even though
- * the APK is now being signed by a newer signing certificate.
- */
- public Builder setOriginalCapabilities(SignerCapabilities signerCapabilities) {
- if (signerCapabilities == null) {
- throw new NullPointerException("signerCapabilities == null");
- }
- mOriginalCapabilities = signerCapabilities;
- return this;
- }
-
- /**
- * Sets capabilities to give {@code mNewSignerConfig}. These capabilities allow an
- * older signing certificate to still be used in some situations on the platform even though
- * the APK is now being signed by a newer signing certificate. By default, the new signer
- * will have all capabilities, so when first switching to a new signing certificate, these
- * capabilities have no effect, but they will act as the default level of trust when moving
- * to a new signing certificate.
- */
- public Builder setNewCapabilities(SignerCapabilities signerCapabilities) {
- if (signerCapabilities == null) {
- throw new NullPointerException("signerCapabilities == null");
- }
- mNewCapabilities = signerCapabilities;
- return this;
- }
-
- public SigningCertificateLineage build()
- throws CertificateEncodingException, InvalidKeyException, NoSuchAlgorithmException,
- SignatureException {
- if (mMinSdkVersion < AndroidSdkVersion.P) {
- mMinSdkVersion = AndroidSdkVersion.P;
- }
-
- if (mOriginalCapabilities == null) {
- mOriginalCapabilities = new SignerCapabilities.Builder().build();
- }
-
- if (mNewCapabilities == null) {
- mNewCapabilities = new SignerCapabilities.Builder().build();
- }
-
- return createSigningLineage(
- mMinSdkVersion, mOriginalSignerConfig, mOriginalCapabilities,
- mNewSignerConfig, mNewCapabilities);
- }
- }
-}
diff --git a/app/src/main/java/com/android/apksig/SourceStampVerifier.java b/app/src/main/java/com/android/apksig/SourceStampVerifier.java
deleted file mode 100644
index 587cbd31fe..0000000000
--- a/app/src/main/java/com/android/apksig/SourceStampVerifier.java
+++ /dev/null
@@ -1,882 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.apksig;
-
-import static com.android.apksig.Constants.VERSION_APK_SIGNATURE_SCHEME_V2;
-import static com.android.apksig.Constants.VERSION_APK_SIGNATURE_SCHEME_V3;
-import static com.android.apksig.Constants.VERSION_JAR_SIGNATURE_SCHEME;
-import static com.android.apksig.apk.ApkUtilsLite.computeSha256DigestBytes;
-import static com.android.apksig.internal.apk.stamp.SourceStampConstants.SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME;
-import static com.android.apksig.internal.apk.v1.V1SchemeConstants.MANIFEST_ENTRY_NAME;
-
-import com.android.apksig.apk.ApkFormatException;
-import com.android.apksig.apk.ApkUtilsLite;
-import com.android.apksig.internal.apk.ApkSigResult;
-import com.android.apksig.internal.apk.ApkSignerInfo;
-import com.android.apksig.internal.apk.ApkSigningBlockUtilsLite;
-import com.android.apksig.internal.apk.ContentDigestAlgorithm;
-import com.android.apksig.internal.apk.SignatureAlgorithm;
-import com.android.apksig.internal.apk.SignatureInfo;
-import com.android.apksig.internal.apk.SignatureNotFoundException;
-import com.android.apksig.internal.apk.stamp.SourceStampConstants;
-import com.android.apksig.internal.apk.stamp.V2SourceStampVerifier;
-import com.android.apksig.internal.apk.v2.V2SchemeConstants;
-import com.android.apksig.internal.apk.v3.V3SchemeConstants;
-import com.android.apksig.internal.util.AndroidSdkVersion;
-import com.android.apksig.internal.util.GuaranteedEncodedFormX509Certificate;
-import com.android.apksig.internal.zip.CentralDirectoryRecord;
-import com.android.apksig.internal.zip.LocalFileRecord;
-import com.android.apksig.internal.zip.ZipUtils;
-import com.android.apksig.util.DataSource;
-import com.android.apksig.util.DataSources;
-import com.android.apksig.zip.ZipFormatException;
-import com.android.apksig.zip.ZipSections;
-
-import java.io.ByteArrayInputStream;
-import java.io.Closeable;
-import java.io.File;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.nio.BufferUnderflowException;
-import java.nio.ByteBuffer;
-import java.security.NoSuchAlgorithmException;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.EnumMap;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * APK source stamp verifier intended only to verify the validity of the stamp signature.
- *
- *
Note, this verifier does not validate the signatures of the jar signing / APK signature blocks
- * when obtaining the digests for verification. This verifier should only be used in cases where
- * another mechanism has already been used to verify the APK signatures.
- */
-public class SourceStampVerifier {
- private final File mApkFile;
- private final DataSource mApkDataSource;
-
- private final int mMinSdkVersion;
- private final int mMaxSdkVersion;
-
- private SourceStampVerifier(
- File apkFile,
- DataSource apkDataSource,
- int minSdkVersion,
- int maxSdkVersion) {
- mApkFile = apkFile;
- mApkDataSource = apkDataSource;
- mMinSdkVersion = minSdkVersion;
- mMaxSdkVersion = maxSdkVersion;
- }
-
- /**
- * Verifies the APK's source stamp signature and returns the result of the verification.
- *
- *
The APK's source stamp can be considered verified if the result's {@link
- * Result#isVerified()} returns {@code true}. If source stamp verification fails all of the
- * resulting errors can be obtained from {@link Result#getAllErrors()}, or individual errors
- * can be obtained as follows:
- *
- * - Obtain the generic errors via {@link Result#getErrors()}
- *
- Obtain the V2 signers via {@link Result#getV2SchemeSigners()}, then for each signer
- * query for any errors with {@link Result.SignerInfo#getErrors()}
- *
- Obtain the V3 signers via {@link Result#getV3SchemeSigners()}, then for each signer
- * query for any errors with {@link Result.SignerInfo#getErrors()}
- *
- Obtain the source stamp signer via {@link Result#getSourceStampInfo()}, then query
- * for any stamp errors with {@link Result.SourceStampInfo#getErrors()}
- *
- */
- public SourceStampVerifier.Result verifySourceStamp() {
- return verifySourceStamp(null);
- }
-
- /**
- * Verifies the APK's source stamp signature, including verification that the SHA-256 digest of
- * the stamp signing certificate matches the {@code expectedCertDigest}, and returns the result
- * of the verification.
- *
- * A value of {@code null} for the {@code expectedCertDigest} will verify the source stamp,
- * if present, without verifying the actual source stamp certificate used to sign the source
- * stamp. This can be used to verify an APK contains a properly signed source stamp without
- * verifying a particular signer.
- *
- * @see #verifySourceStamp()
- */
- public SourceStampVerifier.Result verifySourceStamp(String expectedCertDigest) {
- Closeable in = null;
- try {
- DataSource apk;
- if (mApkDataSource != null) {
- apk = mApkDataSource;
- } else if (mApkFile != null) {
- RandomAccessFile f = new RandomAccessFile(mApkFile, "r");
- in = f;
- apk = DataSources.asDataSource(f, 0, f.length());
- } else {
- throw new IllegalStateException("APK not provided");
- }
- return verifySourceStamp(apk, expectedCertDigest);
- } catch (IOException e) {
- Result result = new Result();
- result.addVerificationError(ApkVerificationIssue.UNEXPECTED_EXCEPTION, e);
- return result;
- } finally {
- if (in != null) {
- try {
- in.close();
- } catch (IOException ignored) {
- }
- }
- }
- }
-
- /**
- * Verifies the provided {@code apk}'s source stamp signature, including verification of the
- * SHA-256 digest of the stamp signing certificate matches the {@code expectedCertDigest}, and
- * returns the result of the verification.
- *
- * @see #verifySourceStamp(String)
- */
- private SourceStampVerifier.Result verifySourceStamp(DataSource apk,
- String expectedCertDigest) {
- Result result = new Result();
- try {
- ZipSections zipSections = ApkUtilsLite.findZipSections(apk);
- // Attempt to obtain the source stamp's certificate digest from the APK.
- List cdRecords =
- ZipUtils.parseZipCentralDirectory(apk, zipSections);
- CentralDirectoryRecord sourceStampCdRecord = null;
- for (CentralDirectoryRecord cdRecord : cdRecords) {
- if (SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME.equals(cdRecord.getName())) {
- sourceStampCdRecord = cdRecord;
- break;
- }
- }
-
- // If the source stamp's certificate digest is not available within the APK then the
- // source stamp cannot be verified; check if a source stamp signing block is in the
- // APK's signature block to determine the appropriate status to return.
- if (sourceStampCdRecord == null) {
- boolean stampSigningBlockFound;
- try {
- ApkSigningBlockUtilsLite.findSignature(apk, zipSections,
- SourceStampConstants.V2_SOURCE_STAMP_BLOCK_ID);
- stampSigningBlockFound = true;
- } catch (SignatureNotFoundException e) {
- stampSigningBlockFound = false;
- }
- result.addVerificationError(stampSigningBlockFound
- ? ApkVerificationIssue.SOURCE_STAMP_SIGNATURE_BLOCK_WITHOUT_CERT_DIGEST
- : ApkVerificationIssue.SOURCE_STAMP_CERT_DIGEST_AND_SIG_BLOCK_MISSING);
- return result;
- }
-
- // Verify that the contents of the source stamp certificate digest match the expected
- // value, if provided.
- byte[] sourceStampCertificateDigest =
- LocalFileRecord.getUncompressedData(
- apk,
- sourceStampCdRecord,
- zipSections.getZipCentralDirectoryOffset());
- if (expectedCertDigest != null) {
- String actualCertDigest = ApkSigningBlockUtilsLite.toHex(
- sourceStampCertificateDigest);
- if (!expectedCertDigest.equalsIgnoreCase(actualCertDigest)) {
- result.addVerificationError(
- ApkVerificationIssue.SOURCE_STAMP_EXPECTED_DIGEST_MISMATCH,
- actualCertDigest, expectedCertDigest);
- return result;
- }
- }
-
- Map> signatureSchemeApkContentDigests =
- new HashMap<>();
- if (mMaxSdkVersion >= AndroidSdkVersion.P) {
- SignatureInfo signatureInfo;
- try {
- signatureInfo = ApkSigningBlockUtilsLite.findSignature(apk, zipSections,
- V3SchemeConstants.APK_SIGNATURE_SCHEME_V3_BLOCK_ID);
- } catch (SignatureNotFoundException e) {
- signatureInfo = null;
- }
- if (signatureInfo != null) {
- Map apkContentDigests = new EnumMap<>(
- ContentDigestAlgorithm.class);
- parseSigners(signatureInfo.signatureBlock, VERSION_APK_SIGNATURE_SCHEME_V3,
- apkContentDigests, result);
- signatureSchemeApkContentDigests.put(
- VERSION_APK_SIGNATURE_SCHEME_V3, apkContentDigests);
- }
- }
-
- if (mMaxSdkVersion >= AndroidSdkVersion.N && (mMinSdkVersion < AndroidSdkVersion.P ||
- signatureSchemeApkContentDigests.isEmpty())) {
- SignatureInfo signatureInfo;
- try {
- signatureInfo = ApkSigningBlockUtilsLite.findSignature(apk, zipSections,
- V2SchemeConstants.APK_SIGNATURE_SCHEME_V2_BLOCK_ID);
- } catch (SignatureNotFoundException e) {
- signatureInfo = null;
- }
- if (signatureInfo != null) {
- Map apkContentDigests = new EnumMap<>(
- ContentDigestAlgorithm.class);
- parseSigners(signatureInfo.signatureBlock, VERSION_APK_SIGNATURE_SCHEME_V2,
- apkContentDigests, result);
- signatureSchemeApkContentDigests.put(
- VERSION_APK_SIGNATURE_SCHEME_V2, apkContentDigests);
- }
- }
-
- if (mMinSdkVersion < AndroidSdkVersion.N
- || signatureSchemeApkContentDigests.isEmpty()) {
- Map apkContentDigests =
- getApkContentDigestFromV1SigningScheme(cdRecords, apk, zipSections, result);
- signatureSchemeApkContentDigests.put(VERSION_JAR_SIGNATURE_SCHEME,
- apkContentDigests);
- }
-
- ApkSigResult sourceStampResult =
- V2SourceStampVerifier.verify(
- apk,
- zipSections,
- sourceStampCertificateDigest,
- signatureSchemeApkContentDigests,
- mMinSdkVersion,
- mMaxSdkVersion);
- result.mergeFrom(sourceStampResult);
- return result;
- } catch (ApkFormatException | IOException | ZipFormatException e) {
- result.addVerificationError(ApkVerificationIssue.MALFORMED_APK, e);
- } catch (NoSuchAlgorithmException e) {
- result.addVerificationError(ApkVerificationIssue.UNEXPECTED_EXCEPTION, e);
- } catch (SignatureNotFoundException e) {
- result.addVerificationError(ApkVerificationIssue.SOURCE_STAMP_SIG_MISSING);
- }
- return result;
- }
-
- /**
- * Parses each signer in the provided APK V2 / V3 signature block and populates corresponding
- * {@code SignerInfo} of the provided {@code result} and their {@code apkContentDigests}.
- *
- * This method adds one or more errors to the {@code result} if a verification error is
- * expected to be encountered on an Android platform version in the
- * {@code [minSdkVersion, maxSdkVersion]} range.
- */
- public static void parseSigners(
- ByteBuffer apkSignatureSchemeBlock,
- int apkSigSchemeVersion,
- Map apkContentDigests,
- Result result) {
- boolean isV2Block = apkSigSchemeVersion == VERSION_APK_SIGNATURE_SCHEME_V2;
- // Both the V2 and V3 signature blocks contain the following:
- // * length-prefixed sequence of length-prefixed signers
- ByteBuffer signers;
- try {
- signers = ApkSigningBlockUtilsLite.getLengthPrefixedSlice(apkSignatureSchemeBlock);
- } catch (ApkFormatException e) {
- result.addVerificationWarning(isV2Block ? ApkVerificationIssue.V2_SIG_MALFORMED_SIGNERS
- : ApkVerificationIssue.V3_SIG_MALFORMED_SIGNERS);
- return;
- }
- if (!signers.hasRemaining()) {
- result.addVerificationWarning(isV2Block ? ApkVerificationIssue.V2_SIG_NO_SIGNERS
- : ApkVerificationIssue.V3_SIG_NO_SIGNERS);
- return;
- }
-
- CertificateFactory certFactory;
- try {
- certFactory = CertificateFactory.getInstance("X.509");
- } catch (CertificateException e) {
- throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
- }
- while (signers.hasRemaining()) {
- Result.SignerInfo signerInfo = new Result.SignerInfo();
- if (isV2Block) {
- result.addV2Signer(signerInfo);
- } else {
- result.addV3Signer(signerInfo);
- }
- try {
- ByteBuffer signer = ApkSigningBlockUtilsLite.getLengthPrefixedSlice(signers);
- parseSigner(
- signer,
- apkSigSchemeVersion,
- certFactory,
- apkContentDigests,
- signerInfo);
- } catch (ApkFormatException | BufferUnderflowException e) {
- signerInfo.addVerificationWarning(
- isV2Block ? ApkVerificationIssue.V2_SIG_MALFORMED_SIGNER
- : ApkVerificationIssue.V3_SIG_MALFORMED_SIGNER);
- return;
- }
- }
- }
-
- /**
- * Parses the provided signer block and populates the {@code result}.
- *
- * This verifies signatures over {@code signed-data} contained in this block but does not
- * verify the integrity of the rest of the APK. To facilitate APK integrity verification, this
- * method adds the {@code contentDigestsToVerify}. These digests can then be used to verify the
- * integrity of the APK.
- *
- *
This method adds one or more errors to the {@code result} if a verification error is
- * expected to be encountered on an Android platform version in the
- * {@code [minSdkVersion, maxSdkVersion]} range.
- */
- private static void parseSigner(
- ByteBuffer signerBlock,
- int apkSigSchemeVersion,
- CertificateFactory certFactory,
- Map apkContentDigests,
- Result.SignerInfo signerInfo)
- throws ApkFormatException {
- boolean isV2Signer = apkSigSchemeVersion == VERSION_APK_SIGNATURE_SCHEME_V2;
- // Both the V2 and V3 signer blocks contain the following:
- // * length-prefixed signed data
- // * length-prefixed sequence of length-prefixed digests:
- // * uint32: signature algorithm ID
- // * length-prefixed bytes: digest of contents
- // * length-prefixed sequence of certificates:
- // * length-prefixed bytes: X.509 certificate (ASN.1 DER encoded).
- ByteBuffer signedData = ApkSigningBlockUtilsLite.getLengthPrefixedSlice(signerBlock);
- ByteBuffer digests = ApkSigningBlockUtilsLite.getLengthPrefixedSlice(signedData);
- ByteBuffer certificates = ApkSigningBlockUtilsLite.getLengthPrefixedSlice(signedData);
-
- // Parse the digests block
- while (digests.hasRemaining()) {
- try {
- ByteBuffer digest = ApkSigningBlockUtilsLite.getLengthPrefixedSlice(digests);
- int sigAlgorithmId = digest.getInt();
- byte[] digestBytes = ApkSigningBlockUtilsLite.readLengthPrefixedByteArray(digest);
- SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.findById(sigAlgorithmId);
- if (signatureAlgorithm == null) {
- continue;
- }
- apkContentDigests.put(signatureAlgorithm.getContentDigestAlgorithm(), digestBytes);
- } catch (ApkFormatException | BufferUnderflowException e) {
- signerInfo.addVerificationWarning(
- isV2Signer ? ApkVerificationIssue.V2_SIG_MALFORMED_DIGEST
- : ApkVerificationIssue.V3_SIG_MALFORMED_DIGEST);
- return;
- }
- }
-
- // Parse the certificates block
- if (certificates.hasRemaining()) {
- byte[] encodedCert = ApkSigningBlockUtilsLite.readLengthPrefixedByteArray(certificates);
- X509Certificate certificate;
- try {
- certificate = (X509Certificate) certFactory.generateCertificate(
- new ByteArrayInputStream(encodedCert));
- } catch (CertificateException e) {
- signerInfo.addVerificationWarning(
- isV2Signer ? ApkVerificationIssue.V2_SIG_MALFORMED_CERTIFICATE
- : ApkVerificationIssue.V3_SIG_MALFORMED_CERTIFICATE);
- return;
- }
- // Wrap the cert so that the result's getEncoded returns exactly the original encoded
- // form. Without this, getEncoded may return a different form from what was stored in
- // the signature. This is because some X509Certificate(Factory) implementations
- // re-encode certificates.
- certificate = new GuaranteedEncodedFormX509Certificate(certificate, encodedCert);
- signerInfo.setSigningCertificate(certificate);
- }
-
- if (signerInfo.getSigningCertificate() == null) {
- signerInfo.addVerificationWarning(
- isV2Signer ? ApkVerificationIssue.V2_SIG_NO_CERTIFICATES
- : ApkVerificationIssue.V3_SIG_NO_CERTIFICATES);
- return;
- }
- }
-
- /**
- * Returns a mapping of the {@link ContentDigestAlgorithm} to the {@code byte[]} digest of the
- * V1 / jar signing META-INF/MANIFEST.MF; if this file is not found then an empty {@code Map} is
- * returned.
- *
- * If any errors are encountered while parsing the V1 signers the provided {@code result}
- * will be updated to include a warning, but the source stamp verification can still proceed.
- */
- private static Map getApkContentDigestFromV1SigningScheme(
- List cdRecords,
- DataSource apk,
- ZipSections zipSections,
- Result result)
- throws IOException, ApkFormatException {
- CentralDirectoryRecord manifestCdRecord = null;
- List signatureBlockRecords = new ArrayList<>(1);
- Map v1ContentDigest = new EnumMap<>(
- ContentDigestAlgorithm.class);
- for (CentralDirectoryRecord cdRecord : cdRecords) {
- String cdRecordName = cdRecord.getName();
- if (cdRecordName == null) {
- continue;
- }
- if (manifestCdRecord == null && MANIFEST_ENTRY_NAME.equals(cdRecordName)) {
- manifestCdRecord = cdRecord;
- continue;
- }
- if (cdRecordName.startsWith("META-INF/")
- && (cdRecordName.endsWith(".RSA")
- || cdRecordName.endsWith(".DSA")
- || cdRecordName.endsWith(".EC"))) {
- signatureBlockRecords.add(cdRecord);
- }
- }
- if (manifestCdRecord == null) {
- // No JAR signing manifest file found. For SourceStamp verification, returning an empty
- // digest is enough since this would affect the final digest signed by the stamp, and
- // thus an empty digest will invalidate that signature.
- return v1ContentDigest;
- }
- if (signatureBlockRecords.isEmpty()) {
- result.addVerificationWarning(ApkVerificationIssue.JAR_SIG_NO_SIGNATURES);
- } else {
- for (CentralDirectoryRecord signatureBlockRecord : signatureBlockRecords) {
- try {
- CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
- byte[] signatureBlockBytes = LocalFileRecord.getUncompressedData(apk,
- signatureBlockRecord, zipSections.getZipCentralDirectoryOffset());
- for (Certificate certificate : certFactory.generateCertificates(
- new ByteArrayInputStream(signatureBlockBytes))) {
- // If multiple certificates are found within the signature block only the
- // first is used as the signer of this block.
- if (certificate instanceof X509Certificate) {
- Result.SignerInfo signerInfo = new Result.SignerInfo();
- signerInfo.setSigningCertificate((X509Certificate) certificate);
- result.addV1Signer(signerInfo);
- break;
- }
- }
- } catch (CertificateException e) {
- // Log a warning for the parsing exception but still proceed with the stamp
- // verification.
- result.addVerificationWarning(ApkVerificationIssue.JAR_SIG_PARSE_EXCEPTION,
- signatureBlockRecord.getName(), e);
- break;
- } catch (ZipFormatException e) {
- throw new ApkFormatException("Failed to read APK", e);
- }
- }
- }
- try {
- byte[] manifestBytes =
- LocalFileRecord.getUncompressedData(
- apk, manifestCdRecord, zipSections.getZipCentralDirectoryOffset());
- v1ContentDigest.put(
- ContentDigestAlgorithm.SHA256, computeSha256DigestBytes(manifestBytes));
- return v1ContentDigest;
- } catch (ZipFormatException e) {
- throw new ApkFormatException("Failed to read APK", e);
- }
- }
-
- /**
- * Result of verifying the APK's source stamp signature; this signature can only be considered
- * verified if {@link #isVerified()} returns true.
- */
- public static class Result {
- private final List