aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTreehugger Robot <treehugger-gerrit@google.com>2016-07-07 20:33:04 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2016-07-07 20:33:05 +0000
commitc78147c650bf34b275ebd03eadc538760b90603f (patch)
treed7d6228d25318c462dc4b056be85b6283b558cec
parentaa3525dcf1d3b5779452e89b7693c775f0092c34 (diff)
parent1b09a508bae3158c8496b9cf26d35910fae7954f (diff)
downloadbuild-c78147c650bf34b275ebd03eadc538760b90603f.tar.gz
Merge "Move apksigner library to tools/apksig."
-rw-r--r--tools/apksigner/Android.mk19
-rw-r--r--tools/apksigner/core/Android.mk29
-rw-r--r--tools/apksigner/core/src/com/android/apksigner/core/ApkSigner.java711
-rw-r--r--tools/apksigner/core/src/com/android/apksigner/core/ApkSignerEngine.java417
-rw-r--r--tools/apksigner/core/src/com/android/apksigner/core/ApkVerifier.java1233
-rw-r--r--tools/apksigner/core/src/com/android/apksigner/core/DefaultApkSignerEngine.java900
-rw-r--r--tools/apksigner/core/src/com/android/apksigner/core/apk/ApkUtils.java158
-rw-r--r--tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v1/DigestAlgorithm.java42
-rw-r--r--tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v1/V1SchemeSigner.java620
-rw-r--r--tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v1/V1SchemeVerifier.java1559
-rw-r--r--tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/ContentDigestAlgorithm.java52
-rw-r--r--tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/SignatureAlgorithm.java142
-rw-r--r--tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/V2SchemeSigner.java600
-rw-r--r--tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/V2SchemeVerifier.java955
-rw-r--r--tools/apksigner/core/src/com/android/apksigner/core/internal/jar/ManifestParser.java335
-rw-r--r--tools/apksigner/core/src/com/android/apksigner/core/internal/jar/ManifestWriter.java127
-rw-r--r--tools/apksigner/core/src/com/android/apksigner/core/internal/jar/SignatureFileWriter.java61
-rw-r--r--tools/apksigner/core/src/com/android/apksigner/core/internal/util/AndroidSdkVersion.java39
-rw-r--r--tools/apksigner/core/src/com/android/apksigner/core/internal/util/ByteBufferDataSource.java126
-rw-r--r--tools/apksigner/core/src/com/android/apksigner/core/internal/util/ByteBufferSink.java56
-rw-r--r--tools/apksigner/core/src/com/android/apksigner/core/internal/util/DelegatingX509Certificate.java179
-rw-r--r--tools/apksigner/core/src/com/android/apksigner/core/internal/util/InclusiveIntRange.java89
-rw-r--r--tools/apksigner/core/src/com/android/apksigner/core/internal/util/MessageDigestSink.java52
-rw-r--r--tools/apksigner/core/src/com/android/apksigner/core/internal/util/OutputStreamDataSink.java78
-rw-r--r--tools/apksigner/core/src/com/android/apksigner/core/internal/util/Pair.java81
-rw-r--r--tools/apksigner/core/src/com/android/apksigner/core/internal/util/RandomAccessFileDataSink.java87
-rw-r--r--tools/apksigner/core/src/com/android/apksigner/core/internal/util/RandomAccessFileDataSource.java165
-rw-r--r--tools/apksigner/core/src/com/android/apksigner/core/internal/zip/CentralDirectoryRecord.java281
-rw-r--r--tools/apksigner/core/src/com/android/apksigner/core/internal/zip/EocdRecord.java48
-rw-r--r--tools/apksigner/core/src/com/android/apksigner/core/internal/zip/LocalFileRecord.java540
-rw-r--r--tools/apksigner/core/src/com/android/apksigner/core/internal/zip/ZipUtils.java353
-rw-r--r--tools/apksigner/core/src/com/android/apksigner/core/util/DataSink.java43
-rw-r--r--tools/apksigner/core/src/com/android/apksigner/core/util/DataSinks.java46
-rw-r--r--tools/apksigner/core/src/com/android/apksigner/core/util/DataSource.java98
-rw-r--r--tools/apksigner/core/src/com/android/apksigner/core/util/DataSources.java48
-rw-r--r--tools/apksigner/core/src/com/android/apksigner/core/zip/ZipFormatException.java32
-rw-r--r--tools/signapk/Android.mk2
-rw-r--r--tools/signapk/src/com/android/signapk/SignApk.java12
38 files changed, 7 insertions, 10408 deletions
diff --git a/tools/apksigner/Android.mk b/tools/apksigner/Android.mk
deleted file mode 100644
index a7b4414c5f..0000000000
--- a/tools/apksigner/Android.mk
+++ /dev/null
@@ -1,19 +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.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tools/apksigner/core/Android.mk b/tools/apksigner/core/Android.mk
deleted file mode 100644
index 132a6f1f06..0000000000
--- a/tools/apksigner/core/Android.mk
+++ /dev/null
@@ -1,29 +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.
-#
-LOCAL_PATH := $(call my-dir)
-
-# apksigner library, for signing APKs and verification signatures of APKs
-# ============================================================
-include $(CLEAR_VARS)
-LOCAL_MODULE := apksigner-core
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-# Disable warnnings about our use of internal proprietary OpenJDK API.
-# TODO: Remove this workaround by moving to our own implementation of PKCS #7
-# SignedData block generation, parsing, and verification.
-LOCAL_JAVACFLAGS := -XDignore.symbol.file
-
-include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/ApkSigner.java b/tools/apksigner/core/src/com/android/apksigner/core/ApkSigner.java
deleted file mode 100644
index 2491302cef..0000000000
--- a/tools/apksigner/core/src/com/android/apksigner/core/ApkSigner.java
+++ /dev/null
@@ -1,711 +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.apksigner.core;
-
-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.SignatureException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import com.android.apksigner.core.apk.ApkUtils;
-import com.android.apksigner.core.internal.apk.v2.V2SchemeVerifier;
-import com.android.apksigner.core.internal.util.ByteBufferDataSource;
-import com.android.apksigner.core.internal.util.Pair;
-import com.android.apksigner.core.internal.zip.CentralDirectoryRecord;
-import com.android.apksigner.core.internal.zip.EocdRecord;
-import com.android.apksigner.core.internal.zip.LocalFileRecord;
-import com.android.apksigner.core.internal.zip.ZipUtils;
-import com.android.apksigner.core.util.DataSink;
-import com.android.apksigner.core.util.DataSinks;
-import com.android.apksigner.core.util.DataSource;
-import com.android.apksigner.core.util.DataSources;
-import com.android.apksigner.core.zip.ZipFormatException;
-
-/**
- * APK signer.
- *
- * <p>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.
- *
- * <p>Use {@link Builder} to obtain instances of this signer.
- */
-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 final ApkSignerEngine mSignerEngine;
-
- private final File mInputApkFile;
- private final DataSource mInputApkDataSource;
-
- private final File mOutputApkFile;
- private final DataSink mOutputApkDataSink;
- private final DataSource mOutputApkDataSource;
-
- private ApkSigner(
- ApkSignerEngine signerEngine,
- File inputApkFile,
- DataSource inputApkDataSource,
- File outputApkFile,
- DataSink outputApkDataSink,
- DataSource outputApkDataSource) {
- mSignerEngine = signerEngine;
-
- mInputApkFile = inputApkFile;
- mInputApkDataSource = inputApkDataSource;
-
- mOutputApkFile = outputApkFile;
- mOutputApkDataSink = outputApkDataSink;
- mOutputApkDataSource = outputApkDataSource;
- }
-
- /**
- * 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 ZipFormatException if the input APK is malformed at ZIP format level
- * @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, ZipFormatException, 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(mSignerEngine, inputApk, outputApkOut, outputApkIn);
- } finally {
- if (out != null) {
- out.close();
- }
- }
- } finally {
- if (in != null) {
- in.close();
- }
- }
- }
-
- private static void sign(
- ApkSignerEngine signerEngine,
- DataSource inputApk,
- DataSink outputApkOut,
- DataSource outputApkIn)
- throws IOException, ZipFormatException, NoSuchAlgorithmException,
- InvalidKeyException, SignatureException {
- // Step 1. Find input APK's main ZIP sections
- ApkUtils.ZipSections inputZipSections = ApkUtils.findZipSections(inputApk);
- long apkSigningBlockOffset = -1;
- try {
- Pair<DataSource, Long> apkSigningBlockAndOffset =
- V2SchemeVerifier.findApkSigningBlock(inputApk, inputZipSections);
- signerEngine.inputApkSigningBlock(apkSigningBlockAndOffset.getFirst());
- apkSigningBlockOffset = apkSigningBlockAndOffset.getSecond();
- } catch (V2SchemeVerifier.SignatureNotFoundException 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.
- }
-
- // Step 2. Parse the input APK's ZIP Central Directory
- ByteBuffer inputCd = getZipCentralDirectory(inputApk, inputZipSections);
- List<CentralDirectoryRecord> inputCdRecords =
- parseZipCentralDirectory(inputCd, inputZipSections);
-
- // Step 3. 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<CentralDirectoryRecord> inputCdRecordsSortedByLfhOffset =
- new ArrayList<>(inputCdRecords);
- Collections.sort(
- inputCdRecordsSortedByLfhOffset,
- CentralDirectoryRecord.BY_LOCAL_FILE_HEADER_OFFSET_COMPARATOR);
- DataSource inputApkLfhSection =
- inputApk.slice(
- 0,
- (apkSigningBlockOffset != -1)
- ? apkSigningBlockOffset
- : inputZipSections.getZipCentralDirectoryOffset());
- int lastModifiedDateForNewEntries = -1;
- int lastModifiedTimeForNewEntries = -1;
- long inputOffset = 0;
- long outputOffset = 0;
- Map<String, CentralDirectoryRecord> outputCdRecordsByName =
- new HashMap<>(inputCdRecords.size());
- for (final CentralDirectoryRecord inputCdRecord : inputCdRecordsSortedByLfhOffset) {
- String entryName = inputCdRecord.getName();
- 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 =
- LocalFileRecord.getRecord(
- inputApkLfhSection, inputCdRecord, inputApkLfhSection.size());
- 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;
- long outputLocalFileRecordSize =
- outputInputJarEntryLfhRecordPreservingDataAlignment(
- inputApkLfhSection,
- inputLocalFileRecord,
- outputApkOut,
- outputLocalFileHeaderOffset);
- outputOffset += outputLocalFileRecordSize;
-
- // 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 4. Sort output APK's Central Directory records in the order in which they should
- // appear in the output
- List<CentralDirectoryRecord> 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);
- }
- }
-
- // Step 5. 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) {
- if (lastModifiedDateForNewEntries == -1) {
- lastModifiedDateForNewEntries = 0x3a21; // Jan 1 2009 (DOS)
- lastModifiedTimeForNewEntries = 0;
- }
- for (ApkSignerEngine.OutputJarSignatureRequest.JarEntry entry :
- outputJarSignatureRequest.getAdditionalJarEntries()) {
- String entryName = entry.getName();
- byte[] uncompressedData = entry.getData();
- ZipUtils.DeflateResult deflateResult =
- ZipUtils.deflate(ByteBuffer.wrap(uncompressedData));
- byte[] compressedData = deflateResult.output;
- long uncompressedDataCrc32 = deflateResult.inputCrc32;
-
- ApkSignerEngine.InspectJarEntryRequest inspectEntryRequest =
- signerEngine.outputJarEntry(entryName);
- if (inspectEntryRequest != null) {
- inspectEntryRequest.getDataSink().consume(
- uncompressedData, 0, uncompressedData.length);
- inspectEntryRequest.done();
- }
-
- long localFileHeaderOffset = outputOffset;
- outputOffset +=
- LocalFileRecord.outputRecordWithDeflateCompressedData(
- entryName,
- lastModifiedTimeForNewEntries,
- lastModifiedDateForNewEntries,
- compressedData,
- uncompressedDataCrc32,
- uncompressedData.length,
- outputApkOut);
-
-
- outputCdRecords.add(
- CentralDirectoryRecord.createWithDeflateCompressedData(
- entryName,
- lastModifiedTimeForNewEntries,
- lastModifiedDateForNewEntries,
- uncompressedDataCrc32,
- compressedData.length,
- uncompressedData.length,
- localFileHeaderOffset));
- }
- outputJarSignatureRequest.done();
- }
-
- // Step 6. 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 7. Construct output ZIP End of Central Directory record in an in-memory buffer
- ByteBuffer outputEocd =
- EocdRecord.createWithModifiedCentralDirectoryInfo(
- inputZipSections.getZipEndOfCentralDirectory(),
- outputCentralDirRecordCount,
- outputCentralDirDataSource.size(),
- outputCentralDirStartOffset);
-
- // Step 8. Generate and output APK Signature Scheme v2 signatures, if necessary. This may
- // insert an APK Signing Block just before the output's ZIP Central Directory
- ApkSignerEngine.OutputApkSigningBlockRequest outputApkSigingBlockRequest =
- signerEngine.outputZipSections(
- outputApkIn,
- outputCentralDirDataSource,
- DataSources.asDataSource(outputEocd));
- if (outputApkSigingBlockRequest != null) {
- byte[] outputApkSigningBlock = outputApkSigingBlockRequest.getApkSigningBlock();
- outputApkOut.consume(outputApkSigningBlock, 0, outputApkSigningBlock.length);
- ZipUtils.setZipEocdCentralDirectoryOffset(
- outputEocd, outputCentralDirStartOffset + outputApkSigningBlock.length);
- outputApkSigingBlockRequest.done();
- }
-
- // Step 9. Output ZIP Central Directory and ZIP End of Central Directory
- outputCentralDirDataSource.feed(0, outputCentralDirDataSource.size(), outputApkOut);
- outputApkOut.consume(outputEocd);
- signerEngine.outputDone();
- }
-
- private static void fulfillInspectInputJarEntryRequest(
- DataSource lfhSection,
- LocalFileRecord localFileRecord,
- ApkSignerEngine.InspectJarEntryRequest inspectEntryRequest)
- throws IOException, ZipFormatException {
- localFileRecord.outputUncompressedData(lfhSection, inspectEntryRequest.getDataSink());
- inspectEntryRequest.done();
- }
-
- private static long 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 inputRecord.outputRecord(inputLfhSection, outputLfhSection);
- }
- 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 inputRecord.outputRecord(inputLfhSection, outputLfhSection);
- }
-
- 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 inputRecord.outputRecord(inputLfhSection, outputLfhSection);
- }
-
- // 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);
- return inputRecord.outputRecordWithModifiedExtra(
- inputLfhSection, aligningExtra, outputLfhSection);
- }
-
- 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")) ? 4096 : 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, ZipFormatException {
- long cdSizeBytes = apkSections.getZipCentralDirectorySizeBytes();
- if (cdSizeBytes > Integer.MAX_VALUE) {
- throw new ZipFormatException("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<CentralDirectoryRecord> parseZipCentralDirectory(
- ByteBuffer cd,
- ApkUtils.ZipSections apkSections) throws ZipFormatException {
- long cdOffset = apkSections.getZipCentralDirectoryOffset();
- int expectedCdRecordCount = apkSections.getZipCentralDirectoryRecordCount();
- List<CentralDirectoryRecord> cdRecords = new ArrayList<>(expectedCdRecordCount);
- Set<String> 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 ZipFormatException(
- "Failed to parse ZIP Central Directory record #" + (i + 1)
- + " at file offset " + (cdOffset + offsetInsideCd),
- e);
- }
- String entryName = cdRecord.getName();
- if (!entryNames.add(entryName)) {
- throw new ZipFormatException(
- "Malformed APK: multiple JAR entries with the same name: " + entryName);
- }
- cdRecords.add(cdRecord);
- }
- if (cd.hasRemaining()) {
- throw new ZipFormatException(
- "Unused space at the end of ZIP Central Directory: " + cd.remaining()
- + " bytes starting at file offset " + (cdOffset + cd.position()));
- }
-
- return cdRecords;
- }
-
- /**
- * Builder of {@link ApkSigner} instances.
- *
- * <p>The following information is required to construct a working {@code ApkSigner}:
- * <ul>
- * <li>{@link ApkSignerEngine} -- provided in the constructor,</li>
- * <li>APK to be signed -- see {@link #setInputApk(File) setInputApk} variants,</li>
- * <li>where to store the signed APK -- see {@link #setOutputApk(File) setOutputApk} variants.
- * </li>
- * </ul>
- */
- public static class Builder {
- private final ApkSignerEngine mSignerEngine;
-
- private File mInputApkFile;
- private DataSource mInputApkDataSource;
-
- private File mOutputApkFile;
- private DataSink mOutputApkDataSink;
- private DataSource mOutputApkDataSource;
-
- /**
- * Constructs a new {@code Builder} which will make {@code ApkSigner} use the provided
- * signing engine.
- */
- public Builder(ApkSignerEngine signerEngine) {
- mSignerEngine = signerEngine;
- }
-
- /**
- * 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(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 sink which will receive the output (signed) APK. Data received by the
- * {@code outputApkOut} sink must be visible through the {@code outputApkIn} data source.
- *
- * @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;
- }
-
- /**
- * Returns a new {@code ApkSigner} instance initialized according to the configuration of
- * this builder.
- */
- public ApkSigner build() {
- return new ApkSigner(
- mSignerEngine,
- mInputApkFile,
- mInputApkDataSource,
- mOutputApkFile,
- mOutputApkDataSink,
- mOutputApkDataSource);
- }
- }
-}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/ApkSignerEngine.java b/tools/apksigner/core/src/com/android/apksigner/core/ApkSignerEngine.java
deleted file mode 100644
index 21c2706404..0000000000
--- a/tools/apksigner/core/src/com/android/apksigner/core/ApkSignerEngine.java
+++ /dev/null
@@ -1,417 +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.apksigner.core;
-
-import java.io.Closeable;
-import java.io.IOException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.SignatureException;
-import java.util.List;
-
-import com.android.apksigner.core.util.DataSink;
-import com.android.apksigner.core.util.DataSource;
-
-/**
- * APK signing logic which is independent of how input and output APKs are stored, parsed, and
- * generated.
- *
- * <p><h3>Operating Model</h3>
- *
- * 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.
- *
- * <p>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
- * <a href="#incremental">Incremental Operation</a>.
- *
- * <p>In the engine's operating model, a signed APK is produced as follows.
- * <ol>
- * <li>JAR entries to be signed are output,</li>
- * <li>JAR archive is signed using JAR signing, thus adding the so-called v1 signature to the
- * output,</li>
- * <li>JAR archive is signed using APK Signature Scheme v2, thus adding the so-called v2 signature
- * to the output.</li>
- * </ol>
- *
- * <p>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.
- *
- * <p>To use the engine to sign an input APK (or a collection of JAR entries), follow these
- * steps:
- * <ol>
- * <li>Obtain a new instance of the engine -- engine instances are stateful and thus cannot be used
- * for signing multiple APKs.</li>
- * <li>Locate the input APK's APK Signing Block and provide it to
- * {@link #inputApkSigningBlock(DataSource)}.</li>
- * <li>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.</li>
- * <li>For each output JAR entry, invoke {@link #outputJarEntry(String)} which may request to
- * inspect the entry.</li>
- * <li>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.</li>
- * <li>Locate the ZIP Central Directory and ZIP End of Central Directory sections in the output and
- * invoke {@link #outputZipSections(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.</li>
- * <li>Invoke {@link #outputDone()} to signal that the APK was output in full. The engine will
- * confirm that the output APK is signed.</li>
- * <li>Invoke {@link #close()} to signal that the engine will no longer be used. This lets the
- * engine free any resources it no longer needs.
- * </ol>
- *
- * <p>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.
- *
- * <p><h3 id="incremental">Incremental Operation</h3></a>
- *
- * 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.
- *
- * <p>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.
- *
- * <p><h3>Output-only Operation</h3>
- *
- * 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.
- */
-public interface ApkSignerEngine extends Closeable {
-
- /**
- * 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 IllegalStateException if this engine is closed
- */
- void inputApkSigningBlock(DataSource apkSigningBlock) throws IOException, IllegalStateException;
-
- /**
- * Indicates to this engine that the specified JAR entry was encountered in the input APK.
- *
- * <p>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.
- *
- * <p>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.
- *
- * <p>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 #outputZipSections(DataSource, DataSource, DataSource)} is invoked.
- *
- * @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 NoSuchAlgorithmException, InvalidKeyException, SignatureException,
- IllegalStateException;
-
- /**
- * Indicates to this engine that the ZIP sections comprising the output APK have been output.
- *
- * <p>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 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
- */
- OutputApkSigningBlockRequest outputZipSections(
- DataSource zipEntries,
- DataSource zipCentralDirectory,
- DataSource zipEocd)
- throws IOException, NoSuchAlgorithmException, InvalidKeyException,
- SignatureException, IllegalStateException;
-
- /**
- * Indicates to this engine that the signed APK was output.
- *
- * <p>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;
-
- /**
- * Indicates to this engine that it will no longer be used. Invoking this on an already closed
- * engine is OK.
- *
- * <p>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.
- *
- * <p>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.
- *
- * <p>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.
- *
- * <p>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<JarEntry> 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.
- *
- * <p>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.
- *
- * <p>If the output contains an APK Signing Block, that block must be replaced by the block
- * contained in this request.
- */
- interface OutputApkSigningBlockRequest {
-
- /**
- * Returns the APK Signing Block.
- */
- byte[] getApkSigningBlock();
-
- /**
- * Indicates that the APK Signing Block was output as requested.
- */
- void done();
- }
-}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/ApkVerifier.java b/tools/apksigner/core/src/com/android/apksigner/core/ApkVerifier.java
deleted file mode 100644
index f12b47f977..0000000000
--- a/tools/apksigner/core/src/com/android/apksigner/core/ApkVerifier.java
+++ /dev/null
@@ -1,1233 +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.apksigner.core;
-
-import com.android.apksigner.core.apk.ApkUtils;
-import com.android.apksigner.core.internal.apk.v1.V1SchemeVerifier;
-import com.android.apksigner.core.internal.apk.v2.ContentDigestAlgorithm;
-import com.android.apksigner.core.internal.apk.v2.SignatureAlgorithm;
-import com.android.apksigner.core.internal.apk.v2.V2SchemeVerifier;
-import com.android.apksigner.core.internal.util.AndroidSdkVersion;
-import com.android.apksigner.core.util.DataSource;
-import com.android.apksigner.core.util.DataSources;
-import com.android.apksigner.core.zip.ZipFormatException;
-
-import java.io.Closeable;
-import java.io.File;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-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.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * APK signature verifier which mimics the behavior of the Android platform.
- *
- * <p>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 will verify on Android.
- *
- * <p>Use {@link Builder} to obtain instances of this verifier.
- */
-public class ApkVerifier {
-
- private static final int APK_SIGNATURE_SCHEME_V2_ID = 2;
- private static final Map<Integer, String> SUPPORTED_APK_SIG_SCHEME_NAMES =
- Collections.singletonMap(APK_SIGNATURE_SCHEME_V2_ID, "APK Signature Scheme v2");
-
- private final File mApkFile;
- private final DataSource mApkDataSource;
-
- private final int mMinSdkVersion;
- private final int mMaxSdkVersion;
-
- private ApkVerifier(
- File apkFile,
- DataSource apkDataSource,
- int minSdkVersion,
- int maxSdkVersion) {
- mApkFile = apkFile;
- mApkDataSource = apkDataSource;
- 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.
- *
- * @throws IOException if an I/O error is encountered while reading the APK
- * @throws ZipFormatException if the APK is malformed at ZIP format level
- * @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, ZipFormatException, 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, mMinSdkVersion, mMaxSdkVersion);
- } finally {
- if (in != null) {
- in.close();
- }
- }
- }
-
- /**
- * 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
- * @param minSdkVersion API Level of the oldest Android platform on which the APK's signatures
- * may need to be verified
- * @param maxSdkVersion API Level of the newest Android platform on which the APK's signatures
- * may need to be verified
- *
- * @throws IOException if an I/O error is encountered while reading the APK
- * @throws ZipFormatException if the APK is malformed at ZIP format level
- * @throws NoSuchAlgorithmException if the APK's signatures cannot be verified because a
- * required cryptographic algorithm implementation is missing
- */
- private static Result verify(DataSource apk, int minSdkVersion, int maxSdkVersion)
- throws IOException, ZipFormatException, NoSuchAlgorithmException {
- if (minSdkVersion < 0) {
- throw new IllegalArgumentException(
- "minSdkVersion must not be negative: " + minSdkVersion);
- }
- if (minSdkVersion > maxSdkVersion) {
- throw new IllegalArgumentException(
- "minSdkVersion (" + minSdkVersion + ") > maxSdkVersion (" + maxSdkVersion
- + ")");
- }
- ApkUtils.ZipSections zipSections = ApkUtils.findZipSections(apk);
-
- Result result = new Result();
-
- // Android N and newer attempts to verify APK Signature Scheme v2 signature of the APK.
- // If the signature is not found, it falls back to JAR signature verification. If the
- // signature is found but does not verify, the APK is rejected.
- Set<Integer> foundApkSigSchemeIds;
- if (maxSdkVersion >= AndroidSdkVersion.N) {
- foundApkSigSchemeIds = new HashSet<>(1);
- try {
- V2SchemeVerifier.Result v2Result = V2SchemeVerifier.verify(apk, zipSections);
- foundApkSigSchemeIds.add(APK_SIGNATURE_SCHEME_V2_ID);
- result.mergeFrom(v2Result);
- } catch (V2SchemeVerifier.SignatureNotFoundException ignored) {}
- if (result.containsErrors()) {
- return result;
- }
- } else {
- foundApkSigSchemeIds = Collections.emptySet();
- }
-
- // 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,
- SUPPORTED_APK_SIG_SCHEME_NAMES,
- foundApkSigSchemeIds,
- minSdkVersion,
- maxSdkVersion);
- result.mergeFrom(v1Result);
- }
- 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<Result.V1SchemeSignerInfo> v1Signers =
- new ArrayList<>(result.getV1SchemeSigners());
- ArrayList<Result.V2SchemeSignerInfo> v2Signers =
- new ArrayList<>(result.getV2SchemeSigners());
- ArrayList<ByteArray> v1SignerCerts = new ArrayList<>();
- ArrayList<ByteArray> v2SignerCerts = new ArrayList<>();
- for (Result.V1SchemeSignerInfo signer : v1Signers) {
- try {
- v1SignerCerts.add(new ByteArray(signer.getCertificate().getEncoded()));
- } catch (CertificateEncodingException e) {
- throw new RuntimeException(
- "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 RuntimeException(
- "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 (result.containsErrors()) {
- return result;
- }
-
- // Verified
- result.setVerified();
- 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 considered verified, but has not verified using either v1 or v2 schemes");
- }
-
- return result;
- }
-
- /**
- * 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<IssueWithParams> mErrors = new ArrayList<>();
- private final List<IssueWithParams> mWarnings = new ArrayList<>();
- private final List<X509Certificate> mSignerCerts = new ArrayList<>();
- private final List<V1SchemeSignerInfo> mV1SchemeSigners = new ArrayList<>();
- private final List<V1SchemeSignerInfo> mV1SchemeIgnoredSigners = new ArrayList<>();
- private final List<V2SchemeSignerInfo> mV2SchemeSigners = new ArrayList<>();
-
- private boolean mVerified;
- private boolean mVerifiedUsingV1Scheme;
- private boolean mVerifiedUsingV2Scheme;
-
- /**
- * 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 the verified signers' certificates, one per signer.
- */
- public List<X509Certificate> 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<V1SchemeSignerInfo> 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<V1SchemeSignerInfo> getV1SchemeIgnoredSigners() {
- return mV1SchemeIgnoredSigners;
- }
-
- /**
- * Returns information about APK Signature Scheme v2 signers associated with the APK's
- * signature.
- */
- public List<V2SchemeSignerInfo> getV2SchemeSigners() {
- return mV2SchemeSigners;
- }
-
- /**
- * Returns errors encountered while verifying the APK's signatures.
- */
- public List<IssueWithParams> getErrors() {
- return mErrors;
- }
-
- /**
- * Returns warnings encountered while verifying the APK's signatures.
- */
- public List<IssueWithParams> 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(V2SchemeVerifier.Result source) {
- mVerifiedUsingV2Scheme = source.verified;
- mErrors.addAll(source.getErrors());
- mWarnings.addAll(source.getWarnings());
- for (V2SchemeVerifier.Result.SignerInfo signer : source.signers) {
- mV2SchemeSigners.add(new V2SchemeSignerInfo(signer));
- }
- }
-
- /**
- * 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 (!mV1SchemeSigners.isEmpty()) {
- for (V1SchemeSignerInfo signer : mV1SchemeSigners) {
- if (signer.containsErrors()) {
- return true;
- }
- }
- }
- if (!mV2SchemeSigners.isEmpty()) {
- for (V2SchemeSignerInfo signer : mV2SchemeSigners) {
- if (signer.containsErrors()) {
- return true;
- }
- }
- }
-
- return false;
- }
-
- /**
- * Information about a JAR signer associated with the APK's signature.
- */
- public static class V1SchemeSignerInfo {
- private final String mName;
- private final List<X509Certificate> mCertChain;
- private final String mSignatureBlockFileName;
- private final String mSignatureFileName;
-
- private final List<IssueWithParams> mErrors;
- private final List<IssueWithParams> 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()}.
- *
- * <p>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<X509Certificate> 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<IssueWithParams> 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<IssueWithParams> 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<X509Certificate> mCerts;
-
- private final List<IssueWithParams> mErrors;
- private final List<IssueWithParams> mWarnings;
-
- private V2SchemeSignerInfo(V2SchemeVerifier.Result.SignerInfo result) {
- mIndex = result.index;
- mCerts = result.certs;
- mErrors = result.getErrors();
- mWarnings = result.getWarnings();
- }
-
- /**
- * 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()}.
- *
- * <p>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<X509Certificate> getCertificates() {
- return mCerts;
- }
-
- private void addError(Issue msg, Object... parameters) {
- mErrors.add(new IssueWithParams(msg, parameters));
- }
-
- public boolean containsErrors() {
- return !mErrors.isEmpty();
- }
-
- public List<IssueWithParams> getErrors() {
- return mErrors;
- }
-
- public List<IssueWithParams> getWarnings() {
- return mWarnings;
- }
- }
- }
-
- /**
- * Error or warning encountered while verifying an APK's signatures.
- */
- public static 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.
- *
- * <ul>
- * <li>Parameter 1: name ({@code String})</li>
- * </ul>
- */
- JAR_SIG_DUPLICATE_ZIP_ENTRY("Duplicate entry: %1$s"),
-
- /**
- * JAR manifest contains a section with a duplicate name.
- *
- * <ul>
- * <li>Parameter 1: section name ({@code String})</li>
- * </ul>
- */
- JAR_SIG_DUPLICATE_MANIFEST_SECTION("Duplicate section in META-INF/MANIFEST.MF: %1$s"),
-
- /**
- * JAR manifest contains a section without a name.
- *
- * <ul>
- * <li>Parameter 1: section index (1-based) ({@code Integer})</li>
- * </ul>
- */
- 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.
- *
- * <ul>
- * <li>Parameter 1: signature file name ({@code String})</li>
- * <li>Parameter 2: section index (1-based) ({@code Integer})</li>
- * </ul>
- */
- 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.
- *
- * <ul>
- * <li>Parameter 1: entry name ({@code String})</li>
- * </ul>
- */
- 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.
- *
- * <ul>
- * <li>Parameter 1: entry name ({@code String})</li>
- * </ul>
- */
- 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.
- *
- * <ul>
- * <li>Parameter 1: entry name ({@code String})</li>
- * <li>Parameter 2: signature file name ({@code String})</li>
- * </ul>
- */
- 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.
- *
- * <ul>
- * <li>Parameter 1: entry name ({@code String})</li>
- * </ul>
- */
- 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.
- *
- * <ul>
- * <li>Parameter 1: first entry name ({@code String})</li>
- * <li>Parameter 2: first entry signer names ({@code List<String>})</li>
- * <li>Parameter 3: second entry name ({@code String})</li>
- * <li>Parameter 4: second entry signer names ({@code List<String>})</li>
- * </ul>
- */
- 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.
- *
- * <ul>
- * <li>Parameter 1: entry name ({@code String})</li>
- * <li>Parameter 2: digest algorithm (e.g., SHA-256) ({@code String})</li>
- * <li>Parameter 3: name of the entry in which the expected digest is specified
- * ({@code String})</li>
- * <li>Parameter 4: base64-encoded actual digest ({@code String})</li>
- * <li>Parameter 5: base64-encoded expected digest ({@code String})</li>
- * </ul>
- */
- 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.
- *
- * <ul>
- * <li>Parameter 1: digest algorithm (e.g., SHA-256) ({@code String})</li>
- * <li>Parameter 2: name of the entry in which the expected digest is specified
- * ({@code String})</li>
- * <li>Parameter 3: base64-encoded actual digest ({@code String})</li>
- * <li>Parameter 4: base64-encoded expected digest ({@code String})</li>
- * </ul>
- */
- 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.
- *
- * <ul>
- * <li>Parameter 1: section name ({@code String})</li>
- * <li>Parameter 2: digest algorithm (e.g., SHA-256) ({@code String})</li>
- * <li>Parameter 3: name of the signature file in which the expected digest is specified
- * ({@code String})</li>
- * <li>Parameter 4: base64-encoded actual digest ({@code String})</li>
- * <li>Parameter 5: base64-encoded expected digest ({@code String})</li>
- * </ul>
- */
- 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.
- *
- * <ul>
- * <li>Parameter 1: name of the signature file ({@code String})</li>
- * </ul>
- */
- 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.
- *
- * <ul>
- * <li>Parameter 1: name of the signature file ({@code String})</li>
- * </ul>
- */
- 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.
- *
- * <ul>
- * <li>Parameter 1: name of the encountered file ({@code String})</li>
- * <li>Parameter 2: name of the missing file ({@code String})</li>
- * </ul>
- */
- 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.
- *
- * <ul>
- * <li>Parameter 1: name of the signature block file ({@code String})</li>
- * <li>Parameter 2: name of the signature file ({@code String})</li>
- * <li>Parameter 3: exception ({@code Throwable})</li>
- * </ul>
- */
- JAR_SIG_VERIFY_EXCEPTION("Failed to verify JAR signature %1$s against %2$s: %3$s"),
-
- /**
- * JAR signature contains unsupported digest algorithm.
- *
- * <ul>
- * <li>Parameter 1: name of the signature block file ({@code String})</li>
- * <li>Parameter 2: digest algorithm OID ({@code String})</li>
- * <li>Parameter 2: signature algorithm OID ({@code String})</li>
- * <li>Parameter 3: API Levels on which this combination of algorithms is not supported
- * ({@code String})</li>
- * </ul>
- */
- JAR_SIG_UNSUPPORTED_SIG_ALG(
- "JAR signature %1$s uses digest algorithm %2$s and signature algorithm %3$s which"
- + " is not supported on API Levels %4$s"),
-
- /**
- * An exception was encountered while parsing JAR signature contained in a signature block.
- *
- * <ul>
- * <li>Parameter 1: name of the signature block file ({@code String})</li>
- * <li>Parameter 2: exception ({@code Throwable})</li>
- * </ul>
- */
- 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.
- *
- * <ul>
- * <li>Parameter 1: name of the signature block file ({@code String})</li>
- * <li>Parameter 2: exception ({@code Throwable})</li>
- * </ul>
- */
- 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.
- *
- * <ul>
- * <li>Parameter 1: name of the signature block file ({@code String})</li>
- * <li>Parameter 2: name of the signature file ({@code String})</li>
- * </ul>
- */
- JAR_SIG_DID_NOT_VERIFY("JAR signature %1$s did not verify against %2$s"),
-
- /**
- * JAR signature contains no verified signers.
- *
- * <ul>
- * <li>Parameter 1: name of the signature block file ({@code String})</li>
- * </ul>
- */
- JAR_SIG_NO_SIGNERS("JAR signature %1$s contains no signers"),
-
- /**
- * JAR signature file contains a section with a duplicate name.
- *
- * <ul>
- * <li>Parameter 1: signature file name ({@code String})</li>
- * <li>Parameter 1: section name ({@code String})</li>
- * </ul>
- */
- 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.
- *
- * <ul>
- * <li>Parameter 1: signature file name ({@code String})</li>
- * </ul>
- */
- 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.
- *
- * <ul>
- * <li>Parameter 1: name of the signature file ({@code String})</li>
- * <li>Parameter 2: unknown APK signature scheme ID ({@code} Integer)</li>
- * </ul>
- */
- 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.
- *
- * <ul>
- * <li>Parameter 1: name of the signature file ({@code String})</li>
- * <li>Parameter 2: APK signature scheme ID ({@code} Integer)</li>
- * <li>Parameter 3: APK signature scheme English name ({@code} String)</li>
- * </ul>
- */
- 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.
- *
- * <ul>
- * <li>Parameter 1: entry name ({@code String})</li>
- * </ul>
- */
- 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 APK Signature Scheme v2 signature from this signer despite APK being v2"
- + " signed"),
-
- /**
- * 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 despite APK being v2"
- + " signed"),
-
- /**
- * 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.
- *
- * <ul>
- * <li>Parameter 1: error details ({@code Throwable})</li>
- * </ul>
- */
- V2_SIG_MALFORMED_PUBLIC_KEY("Malformed public key: %1$s"),
-
- /**
- * This APK Signature Scheme v2 signer's certificate could not be parsed.
- *
- * <ul>
- * <li>Parameter 1: index ({@code 0}-based) of the certificate in the signer's list of
- * certificates ({@code Integer})</li>
- * <li>Parameter 2: sequence number ({@code 1}-based) of the certificate in the signer's
- * list of certificates ({@code Integer})</li>
- * <li>Parameter 3: error details ({@code Throwable})</li>
- * </ul>
- */
- 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.
- *
- * <ul>
- * <li>Parameter 1: record number (first record is {@code 1}) ({@code Integer})</li>
- * </ul>
- */
- 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.
- *
- * <ul>
- * <li>Parameter 1: record number (first record is {@code 1}) ({@code Integer})</li>
- * </ul>
- */
- V2_SIG_MALFORMED_DIGEST("Malformed APK Signature Scheme v2 digest record #%1$d"),
-
- /**
- * This APK Signature Scheme v2 signer contains a malformed additional attribute.
- *
- * <ul>
- * <li>Parameter 1: attribute number (first attribute is {@code 1}) {@code Integer})</li>
- * </ul>
- */
- V2_SIG_MALFORMED_ADDITIONAL_ATTRIBUTE("Malformed additional attribute #%1$d"),
-
- /**
- * 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.
- *
- * <ul>
- * <li>Parameter 1: algorithm ID ({@code Integer})</li>
- * </ul>
- */
- V2_SIG_UNKNOWN_SIG_ALGORITHM("Unknown signature algorithm: %1$#x"),
-
- /**
- * This APK Signature Scheme v2 signer contains an unknown additional attribute.
- *
- * <ul>
- * <li>Parameter 1: attribute ID ({@code Integer})</li>
- * </ul>
- */
- 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.
- *
- * <ul>
- * <li>Parameter 1: signature algorithm ({@link SignatureAlgorithm})</li>
- * <li>Parameter 2: exception ({@code Throwable})</li>
- * </ul>
- */
- 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.
- *
- * <ul>
- * <li>Parameter 1: signature algorithm ({@link SignatureAlgorithm})</li>
- * </ul>
- */
- 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"),
-
- /**
- * 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.
- *
- * <ul>
- * <li>Parameter 1: hex-encoded public key from certificate ({@code String})</li>
- * <li>Parameter 2: hex-encoded public key from signatures record ({@code String})</li>
- * </ul>
- */
- 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.
- *
- * <ul>
- * <li>Parameter 1: signature algorithms from signatures record ({@code List<Integer>})</li>
- * <li>Parameter 2: signature algorithms from digests record ({@code List<Integer>})</li>
- * </ul>
- */
- 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.
- *
- * <ul>
- * <li>Parameter 1: content digest algorithm ({@link ContentDigestAlgorithm})</li>
- * <li>Parameter 2: hex-encoded expected digest of the APK ({@code String})</li>
- * <li>Parameter 3: hex-encoded actual digest of the APK ({@code String})</li>
- * </ul>
- */
- V2_SIG_APK_DIGEST_DID_NOT_VERIFY(
- "APK integrity check failed. %1$s digest mismatch."
- + " Expected: <%2$s>, actual: <%3$s>"),
-
- /**
- * APK Signing Block contains an unknown entry.
- *
- * <ul>
- * <li>Parameter 1: entry ID ({@code Integer})</li>
- * </ul>
- */
- APK_SIG_BLOCK_UNKNOWN_ENTRY_ID("APK Signing Block contains unknown entry: ID %1$#x");
-
- private final String mFormat;
-
- private 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 {
- 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) {
- 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 == null) {
- return false;
- }
- if (getClass() != obj.getClass()) {
- 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.
- *
- * <p>Although not required, it is best to provide the SDK version (API Level) of the oldest
- * Android platform on which the APK is supposed to be installed -- see
- * {@link #setMinCheckedPlatformVersion(int)}. Without this information, APKs which use security
- * features not supported on ancient Android platforms (e.g., SHA-256 digests or ECDSA
- * signatures) will not verify.
- */
- public static class Builder {
- private final File mApkFile;
- private final DataSource mApkDataSource;
-
- private int mMinSdkVersion = 1;
- 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.
- *
- * <p>By default, the APK is checked for all platform versions. Thus, APKs which use
- * security features not supported on ancient Android platforms (e.g., SHA-256 digests or
- * ECDSA signatures) will not verify by default.
- *
- * @param minSdkVersion API Level of the oldest platform for which to verify the APK
- *
- * @see #setCheckedPlatformVersions(int, int)
- */
- public Builder setMinCheckedPlatformVersion(int minSdkVersion) {
- mMinSdkVersion = minSdkVersion;
- mMaxSdkVersion = Integer.MAX_VALUE;
- return this;
- }
-
- /**
- * Sets the range of Android platform versions for which the APK is verified. APK
- * verification will confirm that the APK is expected to install successfully on Android
- * platforms whose API Levels fall into this inclusive range.
- *
- * <p>By default, the APK is checked for all platform versions. Thus, APKs which use
- * security features not supported on ancient Android platforms (e.g., SHA-256 digests or
- * ECDSA signatures) will not verify by default.
- *
- * @param minSdkVersion API Level of the oldest platform for which to verify the APK
- * @param maxSdkVersion API Level of the newest platform for which to verify the APK
- *
- * @see #setMinCheckedPlatformVersion(int)
- */
- public Builder setCheckedPlatformVersions(int minSdkVersion, int maxSdkVersion) {
- mMinSdkVersion = minSdkVersion;
- mMaxSdkVersion = maxSdkVersion;
- return this;
- }
-
- /**
- * Returns an {@link ApkVerifier} initialized according to the configuration of this
- * builder.
- */
- public ApkVerifier build() {
- return new ApkVerifier(
- mApkFile,
- mApkDataSource,
- mMinSdkVersion,
- mMaxSdkVersion);
- }
- }
-}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/DefaultApkSignerEngine.java b/tools/apksigner/core/src/com/android/apksigner/core/DefaultApkSignerEngine.java
deleted file mode 100644
index 75b0b20420..0000000000
--- a/tools/apksigner/core/src/com/android/apksigner/core/DefaultApkSignerEngine.java
+++ /dev/null
@@ -1,900 +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.apksigner.core;
-
-import com.android.apksigner.core.internal.apk.v1.DigestAlgorithm;
-import com.android.apksigner.core.internal.apk.v1.V1SchemeSigner;
-import com.android.apksigner.core.internal.apk.v2.V2SchemeSigner;
-import com.android.apksigner.core.internal.util.MessageDigestSink;
-import com.android.apksigner.core.internal.util.Pair;
-import com.android.apksigner.core.util.DataSink;
-import com.android.apksigner.core.util.DataSinks;
-import com.android.apksigner.core.util.DataSource;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-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.CertificateException;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Default implementation of {@link ApkSignerEngine}.
- *
- * <p>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.
-
- private final boolean mV1SigningEnabled;
- private final boolean mV2SigningEnabled;
- private final boolean mOtherSignersSignaturesPreserved;
- private final List<V1SchemeSigner.SignerConfig> mV1SignerConfigs;
- private final DigestAlgorithm mV1ContentDigestAlgorithm;
- private final List<V2SchemeSigner.SignerConfig> mV2SignerConfigs;
-
- private boolean mClosed;
-
- private boolean mV1SignaturePending;
-
- /**
- * Names of JAR entries which this engine is expected to output as part of v1 signing.
- */
- private final Set<String> mSignatureExpectedOutputJarEntryNames;
-
- /** Requests for digests of output JAR entries. */
- private final Map<String, GetJarEntryDataDigestRequest> mOutputJarEntryDigestRequests =
- new HashMap<>();
-
- /** Digests of output JAR entries. */
- private final Map<String, byte[]> mOutputJarEntryDigests = new HashMap<>();
-
- /** Data of JAR entries emitted by this engine as v1 signature. */
- private final Map<String, byte[]> mEmittedSignatureJarEntryData = new HashMap<>();
-
- /** Requests for data of output JAR entries which comprise the v1 signature. */
- private final Map<String, GetJarEntryDataRequest> 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 output the emitted v1 signature or {@code null} if the request hasn't been issued.
- */
- private OutputJarSignatureRequestImpl mAddV1SignatureRequest;
-
- private boolean mV2SignaturePending;
-
- /**
- * Request to output the emitted v2 signature or {@code null} if the request hasn't been issued.
- */
- private OutputApkSigningBlockRequestImpl mAddV2SignatureRequest;
-
- private DefaultApkSignerEngine(
- List<SignerConfig> signerConfigs,
- int minSdkVersion,
- boolean v1SigningEnabled,
- boolean v2SigningEnabled,
- boolean otherSignersSignaturesPreserved) throws InvalidKeyException {
- if (signerConfigs.isEmpty()) {
- throw new IllegalArgumentException("At least one signer config must be provided");
- }
- if (otherSignersSignaturesPreserved) {
- throw new UnsupportedOperationException(
- "Preserving other signer's signatures is not yet implemented");
- }
-
- mV1SigningEnabled = v1SigningEnabled;
- mV2SigningEnabled = v2SigningEnabled;
- mOtherSignersSignaturesPreserved = otherSignersSignaturesPreserved;
- mV1SignerConfigs =
- (v1SigningEnabled)
- ? new ArrayList<>(signerConfigs.size()) : Collections.emptyList();
- mV2SignerConfigs =
- (v2SigningEnabled)
- ? new ArrayList<>(signerConfigs.size()) : Collections.emptyList();
- mV1ContentDigestAlgorithm =
- (v1SigningEnabled)
- ? V1SchemeSigner.getSuggestedContentDigestAlgorithm(minSdkVersion) : null;
- for (SignerConfig signerConfig : signerConfigs) {
- List<X509Certificate> certificates = signerConfig.getCertificates();
- PublicKey publicKey = certificates.get(0).getPublicKey();
-
- if (v1SigningEnabled) {
- DigestAlgorithm v1SignatureDigestAlgorithm =
- V1SchemeSigner.getSuggestedSignatureDigestAlgorithm(
- publicKey, minSdkVersion);
- V1SchemeSigner.SignerConfig v1SignerConfig = new V1SchemeSigner.SignerConfig();
- v1SignerConfig.name = signerConfig.getName();
- v1SignerConfig.privateKey = signerConfig.getPrivateKey();
- v1SignerConfig.certificates = certificates;
- v1SignerConfig.contentDigestAlgorithm = mV1ContentDigestAlgorithm;
- v1SignerConfig.signatureDigestAlgorithm = v1SignatureDigestAlgorithm;
- mV1SignerConfigs.add(v1SignerConfig);
- }
-
- if (v2SigningEnabled) {
- V2SchemeSigner.SignerConfig v2SignerConfig = new V2SchemeSigner.SignerConfig();
- v2SignerConfig.privateKey = signerConfig.getPrivateKey();
- v2SignerConfig.certificates = certificates;
- v2SignerConfig.signatureAlgorithms =
- V2SchemeSigner.getSuggestedSignatureAlgorithms(publicKey, minSdkVersion);
- mV2SignerConfigs.add(v2SignerConfig);
- }
- }
- mSignatureExpectedOutputJarEntryNames =
- (v1SigningEnabled)
- ? V1SchemeSigner.getOutputEntryNames(mV1SignerConfigs)
- : Collections.emptySet();
- }
-
- @Override
- public void inputApkSigningBlock(DataSource apkSigningBlock) {
- checkNotClosed();
-
- if ((apkSigningBlock == null) || (apkSigningBlock.size() == 0)) {
- return;
- }
-
- if (mOtherSignersSignaturesPreserved) {
- // TODO: Preserve blocks other than APK Signature Scheme v2 blocks of signers configured
- // in this engine.
- return;
- }
- // TODO: Preserve blocks other than APK Signature Scheme v2 blocks.
- }
-
- @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 (V1SchemeSigner.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 (!mV1SigningEnabled) {
- // No need to inspect JAR entries when v1 signing is not enabled.
- 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);
- 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 (V1SchemeSigner.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 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());
- }
- mOutputJarEntryDigestRequests.clear();
-
- for (GetJarEntryDataRequest dataRequest : mOutputSignatureJarEntryDataRequests.values()) {
- if (!dataRequest.isDone()) {
- throw new IllegalStateException(
- "Still waiting to inspect output APK's " + dataRequest.getEntryName());
- }
- }
-
- List<Integer> apkSigningSchemeIds =
- (mV2SigningEnabled) ? Collections.singletonList(2) : Collections.emptyList();
- byte[] inputJarManifest =
- (mInputJarManifestEntryDataRequest != null)
- ? mInputJarManifestEntryDataRequest.getData() : null;
-
- // Check whether the most recently used signature (if present) is still fine.
- List<Pair<String, byte[]>> signatureZipEntries;
- if ((mAddV1SignatureRequest == null) || (!mAddV1SignatureRequest.isDone())) {
- try {
- signatureZipEntries =
- V1SchemeSigner.sign(
- mV1SignerConfigs,
- mV1ContentDigestAlgorithm,
- mOutputJarEntryDigests,
- apkSigningSchemeIds,
- inputJarManifest);
- } 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(V1SchemeSigner.MANIFEST_ENTRY_NAME);
- if (!Arrays.equals(newManifest.contents, emittedSignatureManifest)) {
- // Emitted v1 signature is no longer valid.
- try {
- signatureZipEntries =
- V1SchemeSigner.signManifest(
- mV1SignerConfigs,
- mV1ContentDigestAlgorithm,
- apkSigningSchemeIds,
- 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<String, byte[]> 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<OutputJarSignatureRequest.JarEntry> sigEntries =
- new ArrayList<>(signatureZipEntries.size());
- for (Pair<String, byte[]> 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;
- }
-
- @Override
- public OutputApkSigningBlockRequest outputZipSections(
- DataSource zipEntries,
- DataSource zipCentralDirectory,
- DataSource zipEocd)
- throws IOException, InvalidKeyException, SignatureException,
- NoSuchAlgorithmException {
- checkNotClosed();
- checkV1SigningDoneIfEnabled();
- if (!mV2SigningEnabled) {
- return null;
- }
- invalidateV2Signature();
-
- byte[] apkSigningBlock =
- V2SchemeSigner.generateApkSigningBlock(
- zipEntries, zipCentralDirectory, zipEocd, mV2SignerConfigs);
-
- mAddV2SignatureRequest = new OutputApkSigningBlockRequestImpl(apkSigningBlock);
- return mAddV2SignatureRequest;
- }
-
- @Override
- public void outputDone() {
- checkNotClosed();
- checkV1SigningDoneIfEnabled();
- checkV2SigningDoneIfEnabled();
- }
-
- @Override
- public void close() {
- mClosed = true;
-
- mAddV1SignatureRequest = null;
- mInputJarManifestEntryDataRequest = null;
- mOutputJarEntryDigestRequests.clear();
- mOutputJarEntryDigests.clear();
- mEmittedSignatureJarEntryData.clear();
- mOutputSignatureJarEntryDataRequests.clear();
-
- mAddV2SignatureRequest = null;
- }
-
- private void invalidateV1Signature() {
- if (mV1SigningEnabled) {
- mV1SignaturePending = true;
- }
- invalidateV2Signature();
- }
-
- private void invalidateV2Signature() {
- if (mV2SigningEnabled) {
- mV2SignaturePending = true;
- mAddV2SignatureRequest = 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<String, byte[]> 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 checkV2SigningDoneIfEnabled() {
- if (!mV2SignaturePending) {
- return;
- }
- if (mAddV2SignatureRequest == null) {
- throw new IllegalStateException(
- "v2 signature (APK Signature Scheme v2 signature) not yet generated."
- + " Skipped outputZipSections()?");
- }
- if (!mAddV2SignatureRequest.isDone()) {
- throw new IllegalStateException(
- "v2 signature (APK Signature Scheme v2 signature) addition requested by"
- + " outputZipSections() hasn't been fulfilled yet");
- }
- mAddV2SignatureRequest = null;
- mV2SignaturePending = false;
- }
-
- /**
- * 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<JarEntry> mAdditionalJarEntries;
- private volatile boolean mDone;
-
- private OutputJarSignatureRequestImpl(List<JarEntry> additionalZipEntries) {
- mAdditionalJarEntries =
- Collections.unmodifiableList(new ArrayList<>(additionalZipEntries));
- }
-
- @Override
- public List<JarEntry> getAdditionalJarEntries() {
- return mAdditionalJarEntries;
- }
-
- @Override
- public void done() {
- mDone = true;
- }
-
- private boolean isDone() {
- return mDone;
- }
- }
-
- private static class OutputApkSigningBlockRequestImpl implements OutputApkSigningBlockRequest {
- private final byte[] mApkSigningBlock;
- private volatile boolean mDone;
-
- private OutputApkSigningBlockRequestImpl(byte[] apkSigingBlock) {
- mApkSigningBlock = apkSigingBlock.clone();
- }
-
- @Override
- public byte[] getApkSigningBlock() {
- return mApkSigningBlock.clone();
- }
-
- @Override
- public void done() {
- mDone = true;
- }
-
- private boolean isDone() {
- return mDone;
- }
- }
-
- /**
- * 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 = new MessageDigestSink(new MessageDigest[] {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();
- }
- }
- }
-
- /**
- * Configuration of a signer.
- *
- * <p>Use {@link Builder} to obtain configuration instances.
- */
- public static class SignerConfig {
- private final String mName;
- private final PrivateKey mPrivateKey;
- private final List<X509Certificate> mCertificates;
-
- private SignerConfig(
- String name,
- PrivateKey privateKey,
- List<X509Certificate> certificates) {
- mName = name;
- mPrivateKey = privateKey;
- mCertificates = Collections.unmodifiableList(new ArrayList<>(certificates));
- }
-
- /**
- * 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<X509Certificate> getCertificates() {
- return mCertificates;
- }
-
- /**
- * Builder of {@link SignerConfig} instances.
- */
- public static class Builder {
- private final String mName;
- private final PrivateKey mPrivateKey;
- private final List<X509Certificate> mCertificates;
-
- /**
- * 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<X509Certificate> certificates) {
- mName = name;
- mPrivateKey = privateKey;
- mCertificates = new ArrayList<>(certificates);
- }
-
- /**
- * Returns a new {@code SignerConfig} instance configured based on the configuration of
- * this builder.
- */
- public SignerConfig build() {
- return new SignerConfig(
- mName,
- mPrivateKey,
- mCertificates);
- }
- }
- }
-
- /**
- * Builder of {@link DefaultApkSignerEngine} instances.
- */
- public static class Builder {
- private final List<SignerConfig> mSignerConfigs;
- private final int mMinSdkVersion;
-
- private boolean mV1SigningEnabled = true;
- private boolean mV2SigningEnabled = true;
- private boolean mOtherSignersSignaturesPreserved;
-
- /**
- * 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<SignerConfig> signerConfigs,
- int minSdkVersion) {
- if (signerConfigs.isEmpty()) {
- throw new IllegalArgumentException("At least one signer config must be provided");
- }
- 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 {
- return new DefaultApkSignerEngine(
- mSignerConfigs,
- mMinSdkVersion,
- mV1SigningEnabled,
- mV2SigningEnabled,
- mOtherSignersSignaturesPreserved);
- }
-
- /**
- * Sets whether the APK should be signed using JAR signing (aka v1 signature scheme).
- *
- * <p>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).
- *
- * <p>By default, the APK will be signed using this scheme.
- */
- public Builder setV2SigningEnabled(boolean enabled) {
- mV2SigningEnabled = enabled;
- 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.
- *
- * <p>By default, signatures of other signers are omitted from the output APK.
- */
- public Builder setOtherSignersSignaturesPreserved(boolean preserved) {
- mOtherSignersSignaturesPreserved = preserved;
- return this;
- }
- }
-}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/apk/ApkUtils.java b/tools/apksigner/core/src/com/android/apksigner/core/apk/ApkUtils.java
deleted file mode 100644
index 8cc8c90d23..0000000000
--- a/tools/apksigner/core/src/com/android/apksigner/core/apk/ApkUtils.java
+++ /dev/null
@@ -1,158 +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.apksigner.core.apk;
-
-import com.android.apksigner.core.internal.util.Pair;
-import com.android.apksigner.core.internal.zip.ZipUtils;
-import com.android.apksigner.core.util.DataSource;
-import com.android.apksigner.core.zip.ZipFormatException;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-
-/**
- * APK utilities.
- */
-public class ApkUtils {
-
- private ApkUtils() {}
-
- /**
- * Finds the main ZIP sections of the provided APK.
- *
- * @throws IOException if an I/O error occurred while reading the APK
- * @throws ZipFormatException if the APK is malformed
- */
- public static ZipSections findZipSections(DataSource apk)
- throws IOException, ZipFormatException {
- Pair<ByteBuffer, Long> eocdAndOffsetInFile =
- ZipUtils.findZipEndOfCentralDirectoryRecord(apk);
- if (eocdAndOffsetInFile == null) {
- throw new ZipFormatException("ZIP End of Central Directory record not found");
- }
-
- ByteBuffer eocdBuf = eocdAndOffsetInFile.getFirst();
- long eocdOffset = eocdAndOffsetInFile.getSecond();
- if (ZipUtils.isZip64EndOfCentralDirectoryLocatorPresent(apk, eocdOffset)) {
- throw new ZipFormatException("ZIP64 APK not supported");
- }
- eocdBuf.order(ByteOrder.LITTLE_ENDIAN);
- long cdStartOffset = ZipUtils.getZipEocdCentralDirectoryOffset(eocdBuf);
- if (cdStartOffset >= eocdOffset) {
- throw new ZipFormatException(
- "ZIP Central Directory start offset out of range: " + cdStartOffset
- + ". ZIP End of Central Directory offset: " + eocdOffset);
- }
-
- long cdSizeBytes = ZipUtils.getZipEocdCentralDirectorySizeBytes(eocdBuf);
- long cdEndOffset = cdStartOffset + cdSizeBytes;
- if (cdEndOffset > eocdOffset) {
- throw new ZipFormatException(
- "ZIP Central Directory overlaps with End of Central Directory"
- + ". CD end: " + cdEndOffset
- + ", EoCD start: " + eocdOffset);
- }
-
- int cdRecordCount = ZipUtils.getZipEocdCentralDirectoryTotalRecordCount(eocdBuf);
-
- return new ZipSections(
- cdStartOffset,
- cdSizeBytes,
- cdRecordCount,
- eocdOffset,
- eocdBuf);
- }
-
- /**
- * Information about the ZIP sections of an APK.
- */
- public static class ZipSections {
- private final long mCentralDirectoryOffset;
- private final long mCentralDirectorySizeBytes;
- private final int mCentralDirectoryRecordCount;
- private final long mEocdOffset;
- private final ByteBuffer mEocd;
-
- public ZipSections(
- long centralDirectoryOffset,
- long centralDirectorySizeBytes,
- int centralDirectoryRecordCount,
- long eocdOffset,
- ByteBuffer eocd) {
- mCentralDirectoryOffset = centralDirectoryOffset;
- mCentralDirectorySizeBytes = centralDirectorySizeBytes;
- mCentralDirectoryRecordCount = centralDirectoryRecordCount;
- mEocdOffset = eocdOffset;
- mEocd = eocd;
- }
-
- /**
- * Returns the start offset of the ZIP Central Directory. This value is taken from the
- * ZIP End of Central Directory record.
- */
- public long getZipCentralDirectoryOffset() {
- return mCentralDirectoryOffset;
- }
-
- /**
- * Returns the size (in bytes) of the ZIP Central Directory. This value is taken from the
- * ZIP End of Central Directory record.
- */
- public long getZipCentralDirectorySizeBytes() {
- return mCentralDirectorySizeBytes;
- }
-
- /**
- * Returns the number of records in the ZIP Central Directory. This value is taken from the
- * ZIP End of Central Directory record.
- */
- public int getZipCentralDirectoryRecordCount() {
- return mCentralDirectoryRecordCount;
- }
-
- /**
- * Returns the start offset of the ZIP End of Central Directory record. The record extends
- * until the very end of the APK.
- */
- public long getZipEndOfCentralDirectoryOffset() {
- return mEocdOffset;
- }
-
- /**
- * Returns the contents of the ZIP End of Central Directory.
- */
- public ByteBuffer getZipEndOfCentralDirectory() {
- return mEocd;
- }
- }
-
- /**
- * Sets the offset of the start of the ZIP Central Directory in the APK's ZIP End of Central
- * Directory record.
- *
- * @param zipEndOfCentralDirectory APK's ZIP End of Central Directory record
- * @param offset offset of the ZIP Central Directory relative to the start of the archive. Must
- * be between {@code 0} and {@code 2^32 - 1} inclusive.
- */
- public static void setZipEocdCentralDirectoryOffset(
- ByteBuffer zipEndOfCentralDirectory, long offset) {
- ByteBuffer eocd = zipEndOfCentralDirectory.slice();
- eocd.order(ByteOrder.LITTLE_ENDIAN);
- ZipUtils.setZipEocdCentralDirectoryOffset(eocd, offset);
- }
-}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v1/DigestAlgorithm.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v1/DigestAlgorithm.java
deleted file mode 100644
index 71e698b99b..0000000000
--- a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v1/DigestAlgorithm.java
+++ /dev/null
@@ -1,42 +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.apksigner.core.internal.apk.v1;
-
-/**
- * Digest algorithm used with JAR signing (aka v1 signing scheme).
- */
-public enum DigestAlgorithm {
- /** SHA-1 */
- SHA1("SHA-1"),
-
- /** SHA2-256 */
- SHA256("SHA-256");
-
- private final String mJcaMessageDigestAlgorithm;
-
- private DigestAlgorithm(String jcaMessageDigestAlgoritm) {
- mJcaMessageDigestAlgorithm = jcaMessageDigestAlgoritm;
- }
-
- /**
- * Returns the {@link java.security.MessageDigest} algorithm represented by this digest
- * algorithm.
- */
- String getJcaMessageDigestAlgorithm() {
- return mJcaMessageDigestAlgorithm;
- }
-}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v1/V1SchemeSigner.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v1/V1SchemeSigner.java
deleted file mode 100644
index f124d16976..0000000000
--- a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v1/V1SchemeSigner.java
+++ /dev/null
@@ -1,620 +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.apksigner.core.internal.apk.v1;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.security.InvalidKeyException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.Signature;
-import java.security.SignatureException;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateParsingException;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Base64;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-import java.util.SortedMap;
-import java.util.TreeMap;
-import java.util.jar.Attributes;
-import java.util.jar.Manifest;
-
-import sun.security.pkcs.ContentInfo;
-import sun.security.pkcs.PKCS7;
-import sun.security.pkcs.SignerInfo;
-import sun.security.x509.AlgorithmId;
-import sun.security.x509.X500Name;
-
-import com.android.apksigner.core.internal.jar.ManifestWriter;
-import com.android.apksigner.core.internal.jar.SignatureFileWriter;
-import com.android.apksigner.core.internal.util.Pair;
-
-/**
- * APK signer which uses JAR signing (aka v1 signing scheme).
- *
- * @see <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signed_JAR_File">Signed JAR File</a>
- */
-public abstract class V1SchemeSigner {
-
- public static final String MANIFEST_ENTRY_NAME = "META-INF/MANIFEST.MF";
-
- private static final Attributes.Name ATTRIBUTE_NAME_CREATED_BY =
- new Attributes.Name("Created-By");
- private static final String ATTRIBUTE_DEFALT_VALUE_CREATED_BY = "1.0 (Android apksigner)";
- private static final String ATTRIBUTE_VALUE_MANIFEST_VERSION = "1.0";
- private static final String ATTRIBUTE_VALUE_SIGNATURE_VERSION = "1.0";
-
- static final String SF_ATTRIBUTE_NAME_ANDROID_APK_SIGNED_NAME_STR = "X-Android-APK-Signed";
- private static final Attributes.Name SF_ATTRIBUTE_NAME_ANDROID_APK_SIGNED_NAME =
- new Attributes.Name(SF_ATTRIBUTE_NAME_ANDROID_APK_SIGNED_NAME_STR);
-
- /**
- * Signer configuration.
- */
- public static class SignerConfig {
- /** Name. */
- public String name;
-
- /** Private key. */
- public PrivateKey privateKey;
-
- /**
- * Certificates, with the first certificate containing the public key corresponding to
- * {@link #privateKey}.
- */
- public List<X509Certificate> certificates;
-
- /**
- * Digest algorithm used for the signature.
- */
- public DigestAlgorithm signatureDigestAlgorithm;
-
- /**
- * Digest algorithm used for digests of JAR entries and MANIFEST.MF.
- */
- public DigestAlgorithm contentDigestAlgorithm;
- }
-
- /** Hidden constructor to prevent instantiation. */
- private V1SchemeSigner() {}
-
- /**
- * Gets the JAR signing digest algorithm to be used for signing an APK using the provided key.
- *
- * @param minSdkVersion minimum API Level of the platform on which the APK may be installed (see
- * AndroidManifest.xml minSdkVersion attribute)
- *
- * @throws InvalidKeyException if the provided key is not suitable for signing APKs using
- * JAR signing (aka v1 signature scheme)
- */
- public static DigestAlgorithm getSuggestedSignatureDigestAlgorithm(
- PublicKey signingKey, int minSdkVersion) throws InvalidKeyException {
- String keyAlgorithm = signingKey.getAlgorithm();
- if ("RSA".equalsIgnoreCase(keyAlgorithm)) {
- // Prior to API Level 18, only SHA-1 can be used with RSA.
- if (minSdkVersion < 18) {
- return DigestAlgorithm.SHA1;
- }
- return DigestAlgorithm.SHA256;
- } else if ("DSA".equalsIgnoreCase(keyAlgorithm)) {
- // Prior to API Level 21, only SHA-1 can be used with DSA
- if (minSdkVersion < 21) {
- return DigestAlgorithm.SHA1;
- } else {
- return DigestAlgorithm.SHA256;
- }
- } else if ("EC".equalsIgnoreCase(keyAlgorithm)) {
- if (minSdkVersion < 18) {
- throw new InvalidKeyException(
- "ECDSA signatures only supported for minSdkVersion 18 and higher");
- }
- // Prior to API Level 21, only SHA-1 can be used with ECDSA
- if (minSdkVersion < 21) {
- return DigestAlgorithm.SHA1;
- } else {
- return DigestAlgorithm.SHA256;
- }
- } else {
- throw new InvalidKeyException("Unsupported key algorithm: " + keyAlgorithm);
- }
- }
-
- /**
- * Returns the JAR signing digest algorithm to be used for JAR entry digests.
- *
- * @param minSdkVersion minimum API Level of the platform on which the APK may be installed (see
- * AndroidManifest.xml minSdkVersion attribute)
- */
- public static DigestAlgorithm getSuggestedContentDigestAlgorithm(int minSdkVersion) {
- return (minSdkVersion >= 18) ? DigestAlgorithm.SHA256 : DigestAlgorithm.SHA1;
- }
-
- /**
- * Returns a new {@link MessageDigest} instance corresponding to the provided digest algorithm.
- */
- private static MessageDigest getMessageDigestInstance(DigestAlgorithm digestAlgorithm)
- throws NoSuchAlgorithmException {
- String jcaAlgorithm = digestAlgorithm.getJcaMessageDigestAlgorithm();
- return MessageDigest.getInstance(jcaAlgorithm);
- }
-
- /**
- * Returns the JCA {@link MessageDigest} algorithm corresponding to the provided digest
- * algorithm.
- */
- public static String getJcaMessageDigestAlgorithm(DigestAlgorithm digestAlgorithm) {
- return digestAlgorithm.getJcaMessageDigestAlgorithm();
- }
-
- /**
- * Returns {@code true} if the provided JAR entry must be mentioned in signed JAR archive's
- * manifest.
- */
- public static boolean isJarEntryDigestNeededInManifest(String entryName) {
- // See https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signed_JAR_File
-
- // Entries outside of META-INF must be listed in the manifest.
- if (!entryName.startsWith("META-INF/")) {
- return true;
- }
- // Entries in subdirectories of META-INF must be listed in the manifest.
- if (entryName.indexOf('/', "META-INF/".length()) != -1) {
- return true;
- }
-
- // Ignored file names (case-insensitive) in META-INF directory:
- // MANIFEST.MF
- // *.SF
- // *.RSA
- // *.DSA
- // *.EC
- // SIG-*
- String fileNameLowerCase =
- entryName.substring("META-INF/".length()).toLowerCase(Locale.US);
- if (("manifest.mf".equals(fileNameLowerCase))
- || (fileNameLowerCase.endsWith(".sf"))
- || (fileNameLowerCase.endsWith(".rsa"))
- || (fileNameLowerCase.endsWith(".dsa"))
- || (fileNameLowerCase.endsWith(".ec"))
- || (fileNameLowerCase.startsWith("sig-"))) {
- return false;
- }
- return true;
- }
-
- /**
- * Signs the provided APK using JAR signing (aka v1 signature scheme) and returns the list of
- * JAR entries which need to be added to the APK as part of the signature.
- *
- * @param signerConfigs signer configurations, one for each signer. At least one signer config
- * must be provided.
- *
- * @throws NoSuchAlgorithmException if a required cryptographic algorithm implementation is
- * missing
- * @throws InvalidKeyException if a signing key is not suitable for this signature scheme or
- * cannot be used in general
- * @throws SignatureException if an error occurs when computing digests of generating
- * signatures
- */
- public static List<Pair<String, byte[]>> sign(
- List<SignerConfig> signerConfigs,
- DigestAlgorithm jarEntryDigestAlgorithm,
- Map<String, byte[]> jarEntryDigests,
- List<Integer> apkSigningSchemeIds,
- byte[] sourceManifestBytes)
- throws NoSuchAlgorithmException, InvalidKeyException, CertificateException,
- SignatureException {
- if (signerConfigs.isEmpty()) {
- throw new IllegalArgumentException("At least one signer config must be provided");
- }
- OutputManifestFile manifest =
- generateManifestFile(jarEntryDigestAlgorithm, jarEntryDigests, sourceManifestBytes);
-
- return signManifest(signerConfigs, jarEntryDigestAlgorithm, apkSigningSchemeIds, manifest);
- }
-
- /**
- * Signs the provided APK using JAR signing (aka v1 signature scheme) and returns the list of
- * JAR entries which need to be added to the APK as part of the signature.
- *
- * @param signerConfigs signer configurations, one for each signer. At least one signer config
- * must be provided.
- *
- * @throws InvalidKeyException if a signing key is not suitable for this signature scheme or
- * cannot be used in general
- * @throws SignatureException if an error occurs when computing digests of generating
- * signatures
- */
- public static List<Pair<String, byte[]>> signManifest(
- List<SignerConfig> signerConfigs,
- DigestAlgorithm digestAlgorithm,
- List<Integer> apkSigningSchemeIds,
- OutputManifestFile manifest)
- throws NoSuchAlgorithmException, InvalidKeyException, CertificateException,
- SignatureException {
- if (signerConfigs.isEmpty()) {
- throw new IllegalArgumentException("At least one signer config must be provided");
- }
-
- // For each signer output .SF and .(RSA|DSA|EC) file, then output MANIFEST.MF.
- List<Pair<String, byte[]>> signatureJarEntries =
- new ArrayList<>(2 * signerConfigs.size() + 1);
- byte[] sfBytes =
- generateSignatureFile(apkSigningSchemeIds, digestAlgorithm, manifest);
- for (SignerConfig signerConfig : signerConfigs) {
- String signerName = signerConfig.name;
- byte[] signatureBlock;
- try {
- signatureBlock = generateSignatureBlock(signerConfig, sfBytes);
- } catch (InvalidKeyException e) {
- throw new InvalidKeyException(
- "Failed to sign using signer \"" + signerName + "\"", e);
- } catch (CertificateException e) {
- throw new CertificateException(
- "Failed to sign using signer \"" + signerName + "\"", e);
- } catch (SignatureException e) {
- throw new SignatureException(
- "Failed to sign using signer \"" + signerName + "\"", e);
- }
- signatureJarEntries.add(Pair.of("META-INF/" + signerName + ".SF", sfBytes));
- PublicKey publicKey = signerConfig.certificates.get(0).getPublicKey();
- String signatureBlockFileName =
- "META-INF/" + signerName + "."
- + publicKey.getAlgorithm().toUpperCase(Locale.US);
- signatureJarEntries.add(
- Pair.of(signatureBlockFileName, signatureBlock));
- }
- signatureJarEntries.add(Pair.of(MANIFEST_ENTRY_NAME, manifest.contents));
- return signatureJarEntries;
- }
-
- /**
- * Returns the names of JAR entries which this signer will produce as part of v1 signature.
- */
- public static Set<String> getOutputEntryNames(List<SignerConfig> signerConfigs) {
- Set<String> result = new HashSet<>(2 * signerConfigs.size() + 1);
- for (SignerConfig signerConfig : signerConfigs) {
- String signerName = signerConfig.name;
- result.add("META-INF/" + signerName + ".SF");
- PublicKey publicKey = signerConfig.certificates.get(0).getPublicKey();
- String signatureBlockFileName =
- "META-INF/" + signerName + "."
- + publicKey.getAlgorithm().toUpperCase(Locale.US);
- result.add(signatureBlockFileName);
- }
- result.add(MANIFEST_ENTRY_NAME);
- return result;
- }
-
- /**
- * Generated and returns the {@code META-INF/MANIFEST.MF} file based on the provided (optional)
- * input {@code MANIFEST.MF} and digests of JAR entries covered by the manifest.
- */
- public static OutputManifestFile generateManifestFile(
- DigestAlgorithm jarEntryDigestAlgorithm,
- Map<String, byte[]> jarEntryDigests,
- byte[] sourceManifestBytes) {
- Manifest sourceManifest = null;
- if (sourceManifestBytes != null) {
- try {
- sourceManifest = new Manifest(new ByteArrayInputStream(sourceManifestBytes));
- } catch (IOException e) {
- throw new IllegalArgumentException("Failed to parse source MANIFEST.MF", e);
- }
- }
- ByteArrayOutputStream manifestOut = new ByteArrayOutputStream();
- Attributes mainAttrs = new Attributes();
- // Copy the main section from the source manifest (if provided). Otherwise use defaults.
- if (sourceManifest != null) {
- mainAttrs.putAll(sourceManifest.getMainAttributes());
- } else {
- mainAttrs.put(Attributes.Name.MANIFEST_VERSION, ATTRIBUTE_VALUE_MANIFEST_VERSION);
- mainAttrs.put(ATTRIBUTE_NAME_CREATED_BY, ATTRIBUTE_DEFALT_VALUE_CREATED_BY);
- }
-
- try {
- ManifestWriter.writeMainSection(manifestOut, mainAttrs);
- } catch (IOException e) {
- throw new RuntimeException("Failed to write in-memory MANIFEST.MF", e);
- }
-
- List<String> sortedEntryNames = new ArrayList<>(jarEntryDigests.keySet());
- Collections.sort(sortedEntryNames);
- SortedMap<String, byte[]> invidualSectionsContents = new TreeMap<>();
- String entryDigestAttributeName = getEntryDigestAttributeName(jarEntryDigestAlgorithm);
- for (String entryName : sortedEntryNames) {
- byte[] entryDigest = jarEntryDigests.get(entryName);
- Attributes entryAttrs = new Attributes();
- entryAttrs.putValue(
- entryDigestAttributeName,
- Base64.getEncoder().encodeToString(entryDigest));
- ByteArrayOutputStream sectionOut = new ByteArrayOutputStream();
- byte[] sectionBytes;
- try {
- ManifestWriter.writeIndividualSection(sectionOut, entryName, entryAttrs);
- sectionBytes = sectionOut.toByteArray();
- manifestOut.write(sectionBytes);
- } catch (IOException e) {
- throw new RuntimeException("Failed to write in-memory MANIFEST.MF", e);
- }
- invidualSectionsContents.put(entryName, sectionBytes);
- }
-
- OutputManifestFile result = new OutputManifestFile();
- result.contents = manifestOut.toByteArray();
- result.mainSectionAttributes = mainAttrs;
- result.individualSectionsContents = invidualSectionsContents;
- return result;
- }
-
- public static class OutputManifestFile {
- public byte[] contents;
- public SortedMap<String, byte[]> individualSectionsContents;
- public Attributes mainSectionAttributes;
- }
-
- private static byte[] generateSignatureFile(
- List<Integer> apkSignatureSchemeIds,
- DigestAlgorithm manifestDigestAlgorithm,
- OutputManifestFile manifest) throws NoSuchAlgorithmException {
- Manifest sf = new Manifest();
- Attributes mainAttrs = sf.getMainAttributes();
- mainAttrs.put(Attributes.Name.SIGNATURE_VERSION, ATTRIBUTE_VALUE_SIGNATURE_VERSION);
- mainAttrs.put(ATTRIBUTE_NAME_CREATED_BY, ATTRIBUTE_DEFALT_VALUE_CREATED_BY);
- if (!apkSignatureSchemeIds.isEmpty()) {
- // Add APK Signature Scheme v2 (and newer) signature stripping protection.
- // This attribute indicates that this APK is supposed to have been signed using one or
- // more APK-specific signature schemes in addition to the standard JAR signature scheme
- // used by this code. APK signature verifier should reject the APK if it does not
- // contain a signature for the signature scheme the verifier prefers out of this set.
- StringBuilder attrValue = new StringBuilder();
- for (int id : apkSignatureSchemeIds) {
- if (attrValue.length() > 0) {
- attrValue.append(", ");
- }
- attrValue.append(String.valueOf(id));
- }
- mainAttrs.put(
- SF_ATTRIBUTE_NAME_ANDROID_APK_SIGNED_NAME,
- attrValue.toString());
- }
-
- // Add main attribute containing the digest of MANIFEST.MF.
- MessageDigest md = getMessageDigestInstance(manifestDigestAlgorithm);
- mainAttrs.putValue(
- getManifestDigestAttributeName(manifestDigestAlgorithm),
- Base64.getEncoder().encodeToString(md.digest(manifest.contents)));
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- try {
- SignatureFileWriter.writeMainSection(out, mainAttrs);
- } catch (IOException e) {
- throw new RuntimeException("Failed to write in-memory .SF file", e);
- }
- String entryDigestAttributeName = getEntryDigestAttributeName(manifestDigestAlgorithm);
- for (Map.Entry<String, byte[]> manifestSection
- : manifest.individualSectionsContents.entrySet()) {
- String sectionName = manifestSection.getKey();
- byte[] sectionContents = manifestSection.getValue();
- byte[] sectionDigest = md.digest(sectionContents);
- Attributes attrs = new Attributes();
- attrs.putValue(
- entryDigestAttributeName,
- Base64.getEncoder().encodeToString(sectionDigest));
-
- try {
- SignatureFileWriter.writeIndividualSection(out, sectionName, attrs);
- } catch (IOException e) {
- throw new RuntimeException("Failed to write in-memory .SF file", e);
- }
- }
-
- // A bug in the java.util.jar implementation of Android platforms up to version 1.6 will
- // cause a spurious IOException to be thrown if the length of the signature file is a
- // multiple of 1024 bytes. As a workaround, add an extra CRLF in this case.
- if ((out.size() > 0) && ((out.size() % 1024) == 0)) {
- try {
- SignatureFileWriter.writeSectionDelimiter(out);
- } catch (IOException e) {
- throw new RuntimeException("Failed to write to ByteArrayOutputStream", e);
- }
- }
-
- return out.toByteArray();
- }
-
- @SuppressWarnings("restriction")
- private static byte[] generateSignatureBlock(
- SignerConfig signerConfig, byte[] signatureFileBytes)
- throws NoSuchAlgorithmException, InvalidKeyException, CertificateException,
- SignatureException {
- List<X509Certificate> signerCerts = signerConfig.certificates;
- X509Certificate signerCert = signerCerts.get(0);
- PublicKey signerPublicKey = signerCert.getPublicKey();
- DigestAlgorithm digestAlgorithm = signerConfig.signatureDigestAlgorithm;
- Pair<String, AlgorithmId> signatureAlgs =
- getSignerInfoSignatureAlgorithm(signerPublicKey, digestAlgorithm);
- String jcaSignatureAlgorithm = signatureAlgs.getFirst();
- Signature signature = Signature.getInstance(jcaSignatureAlgorithm);
- signature.initSign(signerConfig.privateKey);
- signature.update(signatureFileBytes);
- byte[] signatureBytes = signature.sign();
-
- X500Name issuerName;
- try {
- issuerName = new X500Name(signerCert.getIssuerX500Principal().getName());
- } catch (IOException e) {
- throw new CertificateParsingException(
- "Failed to parse signer certificate issuer name", e);
- }
-
- AlgorithmId digestAlgorithmId = getSignerInfoDigestAlgorithm(digestAlgorithm);
- SignerInfo signerInfo =
- new SignerInfo(
- issuerName,
- signerCert.getSerialNumber(),
- digestAlgorithmId,
- signatureAlgs.getSecond(),
- signatureBytes);
- PKCS7 pkcs7 =
- new PKCS7(
- new AlgorithmId[] {digestAlgorithmId},
- new ContentInfo(ContentInfo.DATA_OID, null),
- signerCerts.toArray(new X509Certificate[signerCerts.size()]),
- new SignerInfo[] {signerInfo});
-
- ByteArrayOutputStream result = new ByteArrayOutputStream();
- try {
- pkcs7.encodeSignedData(result);
- } catch (IOException e) {
- throw new SignatureException("Failed to encode PKCS#7 signed data", e);
- }
- return result.toByteArray();
- }
-
- @SuppressWarnings("restriction")
- private static final AlgorithmId OID_DIGEST_SHA1 = getSupportedAlgorithmId("1.3.14.3.2.26");
- @SuppressWarnings("restriction")
- private static final AlgorithmId OID_DIGEST_SHA256 =
- getSupportedAlgorithmId("2.16.840.1.101.3.4.2.1");
-
- /**
- * Returns the {@code SignerInfo} {@code DigestAlgorithm} to use for {@code SignerInfo} signing
- * using the specified digest algorithm.
- */
- @SuppressWarnings("restriction")
- private static AlgorithmId getSignerInfoDigestAlgorithm(DigestAlgorithm digestAlgorithm) {
- switch (digestAlgorithm) {
- case SHA1:
- return OID_DIGEST_SHA1;
- case SHA256:
- return OID_DIGEST_SHA256;
- default:
- throw new RuntimeException("Unsupported digest algorithm: " + digestAlgorithm);
- }
- }
-
- /**
- * Returns the JCA {@link Signature} algorithm and {@code SignerInfo} {@code SignatureAlgorithm}
- * to use for {@code SignerInfo} which signs with the specified key and digest algorithms.
- */
- @SuppressWarnings("restriction")
- private static Pair<String, AlgorithmId> getSignerInfoSignatureAlgorithm(
- PublicKey publicKey, DigestAlgorithm digestAlgorithm) throws InvalidKeyException {
- // NOTE: This method on purpose uses hard-coded OIDs instead of
- // Algorithm.getId(JCA Signature Algorithm). This is to ensure that the generated SignedData
- // is compatible with all targeted Android platforms and is not dependent on changes in the
- // JCA Signature Algorithm -> OID mappings maintained by AlgorithmId.get(String).
-
- String keyAlgorithm = publicKey.getAlgorithm();
- String digestPrefixForSigAlg;
- switch (digestAlgorithm) {
- case SHA1:
- digestPrefixForSigAlg = "SHA1";
- break;
- case SHA256:
- digestPrefixForSigAlg = "SHA256";
- break;
- default:
- throw new IllegalArgumentException(
- "Unexpected digest algorithm: " + digestAlgorithm);
- }
- if ("RSA".equalsIgnoreCase(keyAlgorithm)) {
- return Pair.of(
- digestPrefixForSigAlg + "withRSA",
- getSupportedAlgorithmId("1.2.840.113549.1.1.1") // RSA encryption
- );
- } else if ("DSA".equalsIgnoreCase(keyAlgorithm)) {
- AlgorithmId sigAlgId;
- switch (digestAlgorithm) {
- case SHA1:
- sigAlgId = getSupportedAlgorithmId("1.2.840.10040.4.1"); // DSA
- break;
- case SHA256:
- // DSA signatures with SHA-256 in SignedData are accepted by Android API Level
- // 21 and higher. However, there are two ways to specify their SignedData
- // SignatureAlgorithm: dsaWithSha256 (2.16.840.1.101.3.4.3.2) and
- // dsa (1.2.840.10040.4.1). The latter works only on API Level 22+. Thus, we use
- // the former.
- sigAlgId =
- getSupportedAlgorithmId("2.16.840.1.101.3.4.3.2"); // DSA with SHA-256
- break;
- default:
- throw new IllegalArgumentException(
- "Unexpected digest algorithm: " + digestAlgorithm);
- }
- return Pair.of(digestPrefixForSigAlg + "withDSA", sigAlgId);
- } else if ("EC".equalsIgnoreCase(keyAlgorithm)) {
- AlgorithmId sigAlgId;
- switch (digestAlgorithm) {
- case SHA1:
- sigAlgId = getSupportedAlgorithmId("1.2.840.10045.4.1"); // ECDSA with SHA-1
- break;
- case SHA256:
- sigAlgId = getSupportedAlgorithmId("1.2.840.10045.4.3.2"); // ECDSA with SHA-256
- break;
- default:
- throw new IllegalArgumentException(
- "Unexpected digest algorithm: " + digestAlgorithm);
- }
- return Pair.of(digestPrefixForSigAlg + "withECDSA", sigAlgId);
- } else {
- throw new InvalidKeyException("Unsupported key algorithm: " + keyAlgorithm);
- }
- }
-
- @SuppressWarnings("restriction")
- private static AlgorithmId getSupportedAlgorithmId(String oid) {
- try {
- return AlgorithmId.get(oid);
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException("Unsupported OID: " + oid, e);
- }
- }
-
- private static String getEntryDigestAttributeName(DigestAlgorithm digestAlgorithm) {
- switch (digestAlgorithm) {
- case SHA1:
- return "SHA1-Digest";
- case SHA256:
- return "SHA-256-Digest";
- default:
- throw new IllegalArgumentException(
- "Unexpected content digest algorithm: " + digestAlgorithm);
- }
- }
-
- private static String getManifestDigestAttributeName(DigestAlgorithm digestAlgorithm) {
- switch (digestAlgorithm) {
- case SHA1:
- return "SHA1-Digest-Manifest";
- case SHA256:
- return "SHA-256-Digest-Manifest";
- default:
- throw new IllegalArgumentException(
- "Unexpected content digest algorithm: " + digestAlgorithm);
- }
- }
-}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v1/V1SchemeVerifier.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v1/V1SchemeVerifier.java
deleted file mode 100644
index 752ba7e02e..0000000000
--- a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v1/V1SchemeVerifier.java
+++ /dev/null
@@ -1,1559 +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.apksigner.core.internal.apk.v1;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.SignatureException;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Base64;
-import java.util.Base64.Decoder;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-import java.util.StringTokenizer;
-import java.util.jar.Attributes;
-
-import com.android.apksigner.core.ApkVerifier.Issue;
-import com.android.apksigner.core.ApkVerifier.IssueWithParams;
-import com.android.apksigner.core.apk.ApkUtils;
-import com.android.apksigner.core.internal.jar.ManifestParser;
-import com.android.apksigner.core.internal.util.AndroidSdkVersion;
-import com.android.apksigner.core.internal.util.InclusiveIntRange;
-import com.android.apksigner.core.internal.util.MessageDigestSink;
-import com.android.apksigner.core.internal.zip.CentralDirectoryRecord;
-import com.android.apksigner.core.internal.zip.LocalFileRecord;
-import com.android.apksigner.core.util.DataSource;
-import com.android.apksigner.core.zip.ZipFormatException;
-
-import sun.security.pkcs.PKCS7;
-import sun.security.pkcs.SignerInfo;
-
-/**
- * APK verifier which uses JAR signing (aka v1 signing scheme).
- *
- * @see <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signed_JAR_File">Signed JAR File</a>
- */
-public abstract class V1SchemeVerifier {
-
- private static final String MANIFEST_ENTRY_NAME = V1SchemeSigner.MANIFEST_ENTRY_NAME;
-
- private V1SchemeVerifier() {}
-
- /**
- * Verifies the provided APK's JAR signatures and returns the result of verification. APK is
- * considered verified only if {@link Result#verified} is {@code true}. If verification fails,
- * the result will contain errors -- see {@link Result#getErrors()}.
- *
- * @throws ZipFormatException if the APK is malformed
- * @throws IOException if an I/O error occurs when reading the APK
- * @throws NoSuchAlgorithmException if the APK's JAR signatures cannot be verified because a
- * required cryptographic algorithm implementation is missing
- */
- public static Result verify(
- DataSource apk,
- ApkUtils.ZipSections apkSections,
- Map<Integer, String> supportedApkSigSchemeNames,
- Set<Integer> foundApkSigSchemeIds,
- int minSdkVersion,
- int maxSdkVersion) throws IOException, ZipFormatException, NoSuchAlgorithmException {
- if (minSdkVersion > maxSdkVersion) {
- throw new IllegalArgumentException(
- "minSdkVersion (" + minSdkVersion + ") > maxSdkVersion (" + maxSdkVersion
- + ")");
- }
-
- Result result = new Result();
-
- // Parse the ZIP Central Directory and check that there are no entries with duplicate names.
- List<CentralDirectoryRecord> cdRecords = parseZipCentralDirectory(apk, apkSections);
- Set<String> cdEntryNames = checkForDuplicateEntries(cdRecords, result);
- if (result.containsErrors()) {
- return result;
- }
-
- // Verify JAR signature(s).
- Signers.verify(
- apk,
- apkSections.getZipCentralDirectoryOffset(),
- cdRecords,
- cdEntryNames,
- supportedApkSigSchemeNames,
- foundApkSigSchemeIds,
- minSdkVersion,
- maxSdkVersion,
- result);
-
- return result;
- }
-
- /**
- * Returns the set of entry names and reports any duplicate entry names in the {@code result}
- * as errors.
- */
- private static Set<String> checkForDuplicateEntries(
- List<CentralDirectoryRecord> cdRecords, Result result) {
- Set<String> cdEntryNames = new HashSet<>(cdRecords.size());
- Set<String> duplicateCdEntryNames = null;
- for (CentralDirectoryRecord cdRecord : cdRecords) {
- String entryName = cdRecord.getName();
- if (!cdEntryNames.add(entryName)) {
- // This is an error. Report this once per duplicate name.
- if (duplicateCdEntryNames == null) {
- duplicateCdEntryNames = new HashSet<>();
- }
- if (duplicateCdEntryNames.add(entryName)) {
- result.addError(Issue.JAR_SIG_DUPLICATE_ZIP_ENTRY, entryName);
- }
- }
- }
- return cdEntryNames;
- }
-
- /**
- * All JAR signers of an APK.
- */
- private static class Signers {
-
- /**
- * Verifies JAR signatures of the provided APK and populates the provided result container
- * with errors, warnings, and information about signers. The APK is considered verified if
- * the {@link Result#verified} is {@code true}.
- */
- private static void verify(
- DataSource apk,
- long cdStartOffset,
- List<CentralDirectoryRecord> cdRecords,
- Set<String> cdEntryNames,
- Map<Integer, String> supportedApkSigSchemeNames,
- Set<Integer> foundApkSigSchemeIds,
- int minSdkVersion,
- int maxSdkVersion,
- Result result) throws ZipFormatException, IOException, NoSuchAlgorithmException {
-
- // Find JAR manifest and signature block files.
- CentralDirectoryRecord manifestEntry = null;
- Map<String, CentralDirectoryRecord> sigFileEntries = new HashMap<>(1);
- List<CentralDirectoryRecord> sigBlockEntries = new ArrayList<>(1);
- for (CentralDirectoryRecord cdRecord : cdRecords) {
- String entryName = cdRecord.getName();
- if (!entryName.startsWith("META-INF/")) {
- continue;
- }
- if ((manifestEntry == null) && (MANIFEST_ENTRY_NAME.equals(entryName))) {
- manifestEntry = cdRecord;
- continue;
- }
- if (entryName.endsWith(".SF")) {
- sigFileEntries.put(entryName, cdRecord);
- continue;
- }
- if ((entryName.endsWith(".RSA"))
- || (entryName.endsWith(".DSA"))
- || (entryName.endsWith(".EC"))) {
- sigBlockEntries.add(cdRecord);
- continue;
- }
- }
- if (manifestEntry == null) {
- result.addError(Issue.JAR_SIG_NO_MANIFEST);
- return;
- }
-
- // Parse the JAR manifest and check that all JAR entries it references exist in the APK.
- byte[] manifestBytes =
- LocalFileRecord.getUncompressedData(apk, manifestEntry, cdStartOffset);
- Map<String, ManifestParser.Section> entryNameToManifestSection = null;
- ManifestParser manifest = new ManifestParser(manifestBytes);
- ManifestParser.Section manifestMainSection = manifest.readSection();
- List<ManifestParser.Section> manifestIndividualSections = manifest.readAllSections();
- entryNameToManifestSection = new HashMap<>(manifestIndividualSections.size());
- int manifestSectionNumber = 0;
- for (ManifestParser.Section manifestSection : manifestIndividualSections) {
- manifestSectionNumber++;
- String entryName = manifestSection.getName();
- if (entryName == null) {
- result.addError(Issue.JAR_SIG_UNNNAMED_MANIFEST_SECTION, manifestSectionNumber);
- continue;
- }
- if (entryNameToManifestSection.put(entryName, manifestSection) != null) {
- result.addError(Issue.JAR_SIG_DUPLICATE_MANIFEST_SECTION, entryName);
- continue;
- }
- if (!cdEntryNames.contains(entryName)) {
- result.addError(
- Issue.JAR_SIG_MISSING_ZIP_ENTRY_REFERENCED_IN_MANIFEST, entryName);
- continue;
- }
- }
- if (result.containsErrors()) {
- return;
- }
- // STATE OF AFFAIRS:
- // * All JAR entries listed in JAR manifest are present in the APK.
-
- // Identify signers
- List<Signer> signers = new ArrayList<>(sigBlockEntries.size());
- for (CentralDirectoryRecord sigBlockEntry : sigBlockEntries) {
- String sigBlockEntryName = sigBlockEntry.getName();
- int extensionDelimiterIndex = sigBlockEntryName.lastIndexOf('.');
- if (extensionDelimiterIndex == -1) {
- throw new RuntimeException(
- "Signature block file name does not contain extension: "
- + sigBlockEntryName);
- }
- String sigFileEntryName =
- sigBlockEntryName.substring(0, extensionDelimiterIndex) + ".SF";
- CentralDirectoryRecord sigFileEntry = sigFileEntries.get(sigFileEntryName);
- if (sigFileEntry == null) {
- result.addWarning(
- Issue.JAR_SIG_MISSING_FILE, sigBlockEntryName, sigFileEntryName);
- continue;
- }
- String signerName = sigBlockEntryName.substring("META-INF/".length());
- Result.SignerInfo signerInfo =
- new Result.SignerInfo(
- signerName, sigBlockEntryName, sigFileEntry.getName());
- Signer signer = new Signer(signerName, sigBlockEntry, sigFileEntry, signerInfo);
- signers.add(signer);
- }
- if (signers.isEmpty()) {
- result.addError(Issue.JAR_SIG_NO_SIGNATURES);
- return;
- }
-
- // Verify each signer's signature block file .(RSA|DSA|EC) against the corresponding
- // signature file .SF. Any error encountered for any signer terminates verification, to
- // mimic Android's behavior.
- for (Signer signer : signers) {
- signer.verifySigBlockAgainstSigFile(
- apk, cdStartOffset, minSdkVersion, maxSdkVersion);
- if (signer.getResult().containsErrors()) {
- result.signers.add(signer.getResult());
- }
- }
- if (result.containsErrors()) {
- return;
- }
- // STATE OF AFFAIRS:
- // * All JAR entries listed in JAR manifest are present in the APK.
- // * All signature files (.SF) verify against corresponding block files (.RSA|.DSA|.EC).
-
- // Verify each signer's signature file (.SF) against the JAR manifest.
- List<Signer> remainingSigners = new ArrayList<>(signers.size());
- for (Signer signer : signers) {
- signer.verifySigFileAgainstManifest(
- manifestBytes,
- manifestMainSection,
- entryNameToManifestSection,
- supportedApkSigSchemeNames,
- foundApkSigSchemeIds,
- minSdkVersion,
- maxSdkVersion);
- if (signer.isIgnored()) {
- result.ignoredSigners.add(signer.getResult());
- } else {
- if (signer.getResult().containsErrors()) {
- result.signers.add(signer.getResult());
- } else {
- remainingSigners.add(signer);
- }
- }
- }
- if (result.containsErrors()) {
- return;
- }
- signers = remainingSigners;
- if (signers.isEmpty()) {
- result.addError(Issue.JAR_SIG_NO_SIGNATURES);
- return;
- }
- // STATE OF AFFAIRS:
- // * All signature files (.SF) verify against corresponding block files (.RSA|.DSA|.EC).
- // * Contents of all JAR manifest sections listed in .SF files verify against .SF files.
- // * All JAR entries listed in JAR manifest are present in the APK.
-
- // Verify data of JAR entries against JAR manifest and .SF files. On Android, an APK's
- // JAR entry is considered signed by signers associated with an .SF file iff the entry
- // is mentioned in the .SF file and the entry's digest(s) mentioned in the JAR manifest
- // match theentry's uncompressed data. Android requires that all such JAR entries are
- // signed by the same set of signers. This set may be smaller than the set of signers
- // we've identified so far.
- Set<Signer> apkSigners =
- verifyJarEntriesAgainstManifestAndSigners(
- apk,
- cdStartOffset,
- cdRecords,
- entryNameToManifestSection,
- signers,
- minSdkVersion,
- maxSdkVersion,
- result);
- if (result.containsErrors()) {
- return;
- }
- // STATE OF AFFAIRS:
- // * All signature files (.SF) verify against corresponding block files (.RSA|.DSA|.EC).
- // * Contents of all JAR manifest sections listed in .SF files verify against .SF files.
- // * All JAR entries listed in JAR manifest are present in the APK.
- // * All JAR entries present in the APK and supposed to be covered by JAR signature
- // (i.e., reside outside of META-INF/) are covered by signatures from the same set
- // of signers.
-
- // Report any JAR entries which aren't covered by signature.
- Set<String> signatureEntryNames = new HashSet<>(1 + result.signers.size() * 2);
- signatureEntryNames.add(manifestEntry.getName());
- for (Signer signer : apkSigners) {
- signatureEntryNames.add(signer.getSignatureBlockEntryName());
- signatureEntryNames.add(signer.getSignatureFileEntryName());
- }
- for (CentralDirectoryRecord cdRecord : cdRecords) {
- String entryName = cdRecord.getName();
- if ((entryName.startsWith("META-INF/"))
- && (!entryName.endsWith("/"))
- && (!signatureEntryNames.contains(entryName))) {
- result.addWarning(Issue.JAR_SIG_UNPROTECTED_ZIP_ENTRY, entryName);
- }
- }
-
- // Reflect the sets of used signers and ignored signers in the result.
- for (Signer signer : signers) {
- if (apkSigners.contains(signer)) {
- result.signers.add(signer.getResult());
- } else {
- result.ignoredSigners.add(signer.getResult());
- }
- }
-
- result.verified = true;
- }
- }
-
- private static class Signer {
- private final String mName;
- private final Result.SignerInfo mResult;
- private final CentralDirectoryRecord mSignatureFileEntry;
- private final CentralDirectoryRecord mSignatureBlockEntry;
- private boolean mIgnored;
-
- private byte[] mSigFileBytes;
- private Set<String> mSigFileEntryNames;
-
- private Signer(
- String name,
- CentralDirectoryRecord sigBlockEntry,
- CentralDirectoryRecord sigFileEntry,
- Result.SignerInfo result) {
- mName = name;
- mResult = result;
- mSignatureBlockEntry = sigBlockEntry;
- mSignatureFileEntry = sigFileEntry;
- }
-
- public String getName() {
- return mName;
- }
-
- public String getSignatureFileEntryName() {
- return mSignatureFileEntry.getName();
- }
-
- public String getSignatureBlockEntryName() {
- return mSignatureBlockEntry.getName();
- }
-
- void setIgnored() {
- mIgnored = true;
- }
-
- public boolean isIgnored() {
- return mIgnored;
- }
-
- public Set<String> getSigFileEntryNames() {
- return mSigFileEntryNames;
- }
-
- public Result.SignerInfo getResult() {
- return mResult;
- }
-
- @SuppressWarnings("restriction")
- public void verifySigBlockAgainstSigFile(
- DataSource apk, long cdStartOffset, int minSdkVersion, int maxSdkVersion)
- throws IOException, ZipFormatException, NoSuchAlgorithmException {
- byte[] sigBlockBytes =
- LocalFileRecord.getUncompressedData(apk, mSignatureBlockEntry, cdStartOffset);
- mSigFileBytes =
- LocalFileRecord.getUncompressedData(apk, mSignatureFileEntry, cdStartOffset);
- PKCS7 sigBlock;
- try {
- sigBlock = new PKCS7(sigBlockBytes);
- } catch (IOException e) {
- if (e.getCause() instanceof CertificateException) {
- mResult.addError(
- Issue.JAR_SIG_MALFORMED_CERTIFICATE, mSignatureBlockEntry.getName(), e);
- } else {
- mResult.addError(
- Issue.JAR_SIG_PARSE_EXCEPTION, mSignatureBlockEntry.getName(), e);
- }
- return;
- }
- SignerInfo[] unverifiedSignerInfos = sigBlock.getSignerInfos();
- if ((unverifiedSignerInfos == null) || (unverifiedSignerInfos.length == 0)) {
- mResult.addError(Issue.JAR_SIG_NO_SIGNERS, mSignatureBlockEntry.getName());
- return;
- }
-
- SignerInfo verifiedSignerInfo = null;
- if ((unverifiedSignerInfos != null) && (unverifiedSignerInfos.length > 0)) {
- for (int i = 0; i < unverifiedSignerInfos.length; i++) {
- SignerInfo unverifiedSignerInfo = unverifiedSignerInfos[i];
- String digestAlgorithmOid =
- unverifiedSignerInfo.getDigestAlgorithmId().getOID().toString();
- String signatureAlgorithmOid =
- unverifiedSignerInfo
- .getDigestEncryptionAlgorithmId().getOID().toString();
- InclusiveIntRange desiredApiLevels =
- InclusiveIntRange.fromTo(minSdkVersion, maxSdkVersion);
- List<InclusiveIntRange> apiLevelsWhereDigestAndSigAlgorithmSupported =
- getSigAlgSupportedApiLevels(digestAlgorithmOid, signatureAlgorithmOid);
- List<InclusiveIntRange> apiLevelsWhereDigestAlgorithmNotSupported =
- desiredApiLevels.getValuesNotIn(apiLevelsWhereDigestAndSigAlgorithmSupported);
- if (!apiLevelsWhereDigestAlgorithmNotSupported.isEmpty()) {
- mResult.addError(
- Issue.JAR_SIG_UNSUPPORTED_SIG_ALG,
- mSignatureBlockEntry.getName(),
- digestAlgorithmOid,
- signatureAlgorithmOid,
- String.valueOf(apiLevelsWhereDigestAlgorithmNotSupported));
- return;
- }
- try {
- verifiedSignerInfo = sigBlock.verify(unverifiedSignerInfo, mSigFileBytes);
- } catch (SignatureException e) {
- mResult.addError(
- Issue.JAR_SIG_VERIFY_EXCEPTION,
- mSignatureBlockEntry.getName(),
- mSignatureFileEntry.getName(),
- e);
- return;
- }
- if (verifiedSignerInfo != null) {
- // Verified
- break;
- }
-
- // Did not verify
- if (minSdkVersion < AndroidSdkVersion.N) {
- // Prior to N, Android attempted to verify only the first SignerInfo.
- mResult.addError(
- Issue.JAR_SIG_DID_NOT_VERIFY,
- mSignatureBlockEntry.getName(),
- mSignatureFileEntry.getName());
- return;
- }
- }
- }
- if (verifiedSignerInfo == null) {
- mResult.addError(Issue.JAR_SIG_NO_SIGNERS, mSignatureBlockEntry.getName());
- return;
- }
-
- List<X509Certificate> certChain;
- try {
- certChain = verifiedSignerInfo.getCertificateChain(sigBlock);
- } catch (IOException e) {
- throw new RuntimeException(
- "Failed to obtain cert chain from " + mSignatureBlockEntry.getName(), e);
- }
- if ((certChain == null) || (certChain.isEmpty())) {
- throw new RuntimeException("Verified SignerInfo does not have a certificate chain");
- }
- mResult.certChain.clear();
- mResult.certChain.addAll(certChain);
- }
-
- private static final String OID_DIGEST_MD5 = "1.2.840.113549.2.5";
- private static final String OID_DIGEST_SHA1 = "1.3.14.3.2.26";
- private static final String OID_DIGEST_SHA224 = "2.16.840.1.101.3.4.2.4";
- private static final String OID_DIGEST_SHA256 = "2.16.840.1.101.3.4.2.1";
- private static final String OID_DIGEST_SHA384 = "2.16.840.1.101.3.4.2.2";
- private static final String OID_DIGEST_SHA512 = "2.16.840.1.101.3.4.2.3";
-
- private static final String OID_SIG_RSA = "1.2.840.113549.1.1.1";
- private static final String OID_SIG_MD5_WITH_RSA = "1.2.840.113549.1.1.4";
- private static final String OID_SIG_SHA1_WITH_RSA = "1.2.840.113549.1.1.5";
- private static final String OID_SIG_SHA224_WITH_RSA = "1.2.840.113549.1.1.14";
- private static final String OID_SIG_SHA256_WITH_RSA = "1.2.840.113549.1.1.11";
- private static final String OID_SIG_SHA384_WITH_RSA = "1.2.840.113549.1.1.12";
- private static final String OID_SIG_SHA512_WITH_RSA = "1.2.840.113549.1.1.13";
-
- private static final String OID_SIG_DSA = "1.2.840.10040.4.1";
- private static final String OID_SIG_SHA1_WITH_DSA = "1.2.840.10040.4.3";
- private static final String OID_SIG_SHA224_WITH_DSA = "2.16.840.1.101.3.4.3.1";
- private static final String OID_SIG_SHA256_WITH_DSA = "2.16.840.1.101.3.4.3.2";
-
- private static final String OID_SIG_SHA1_WITH_ECDSA = "1.2.840.10045.4.1";
- private static final String OID_SIG_SHA224_WITH_ECDSA = "1.2.840.10045.4.3.1";
- private static final String OID_SIG_SHA256_WITH_ECDSA = "1.2.840.10045.4.3.2";
- private static final String OID_SIG_SHA384_WITH_ECDSA = "1.2.840.10045.4.3.3";
- private static final String OID_SIG_SHA512_WITH_ECDSA = "1.2.840.10045.4.3.4";
-
- private static final Map<String, List<InclusiveIntRange>> SUPPORTED_SIG_ALG_OIDS =
- new HashMap<>();
- {
- addSupportedSigAlg(
- OID_DIGEST_MD5, OID_SIG_RSA,
- InclusiveIntRange.from(0));
- addSupportedSigAlg(
- OID_DIGEST_MD5, OID_SIG_MD5_WITH_RSA,
- InclusiveIntRange.fromTo(0, 8), InclusiveIntRange.from(21));
- addSupportedSigAlg(
- OID_DIGEST_MD5, OID_SIG_SHA1_WITH_RSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_MD5, OID_SIG_SHA224_WITH_RSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_MD5, OID_SIG_SHA256_WITH_RSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_MD5, OID_SIG_SHA384_WITH_RSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_MD5, OID_SIG_SHA512_WITH_RSA,
- InclusiveIntRange.fromTo(21, 23));
-
- addSupportedSigAlg(
- OID_DIGEST_SHA1, OID_SIG_RSA,
- InclusiveIntRange.from(0));
- addSupportedSigAlg(
- OID_DIGEST_SHA1, OID_SIG_MD5_WITH_RSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA1, OID_SIG_SHA1_WITH_RSA,
- InclusiveIntRange.from(0));
- addSupportedSigAlg(
- OID_DIGEST_SHA1, OID_SIG_SHA224_WITH_RSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA1, OID_SIG_SHA256_WITH_RSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA1, OID_SIG_SHA384_WITH_RSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA1, OID_SIG_SHA512_WITH_RSA,
- InclusiveIntRange.fromTo(21, 23));
-
- addSupportedSigAlg(
- OID_DIGEST_SHA224, OID_SIG_RSA,
- InclusiveIntRange.fromTo(0, 8), InclusiveIntRange.from(21));
- addSupportedSigAlg(
- OID_DIGEST_SHA224, OID_SIG_MD5_WITH_RSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA224, OID_SIG_SHA1_WITH_RSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA224, OID_SIG_SHA224_WITH_RSA,
- InclusiveIntRange.fromTo(0, 8), InclusiveIntRange.from(21));
- addSupportedSigAlg(
- OID_DIGEST_SHA224, OID_SIG_SHA256_WITH_RSA,
- InclusiveIntRange.fromTo(21, 21));
- addSupportedSigAlg(
- OID_DIGEST_SHA224, OID_SIG_SHA384_WITH_RSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA224, OID_SIG_SHA512_WITH_RSA,
- InclusiveIntRange.fromTo(21, 23));
-
- addSupportedSigAlg(
- OID_DIGEST_SHA256, OID_SIG_RSA,
- InclusiveIntRange.fromTo(0, 8), InclusiveIntRange.from(18));
- addSupportedSigAlg(
- OID_DIGEST_SHA256, OID_SIG_MD5_WITH_RSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA256, OID_SIG_SHA1_WITH_RSA,
- InclusiveIntRange.fromTo(21, 21));
- addSupportedSigAlg(
- OID_DIGEST_SHA256, OID_SIG_SHA224_WITH_RSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA256, OID_SIG_SHA256_WITH_RSA,
- InclusiveIntRange.fromTo(0, 8), InclusiveIntRange.from(18));
- addSupportedSigAlg(
- OID_DIGEST_SHA256, OID_SIG_SHA384_WITH_RSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA256, OID_SIG_SHA512_WITH_RSA,
- InclusiveIntRange.fromTo(21, 23));
-
- addSupportedSigAlg(
- OID_DIGEST_SHA384, OID_SIG_RSA,
- InclusiveIntRange.from(18));
- addSupportedSigAlg(
- OID_DIGEST_SHA384, OID_SIG_MD5_WITH_RSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA384, OID_SIG_SHA1_WITH_RSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA384, OID_SIG_SHA224_WITH_RSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA384, OID_SIG_SHA256_WITH_RSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA384, OID_SIG_SHA384_WITH_RSA,
- InclusiveIntRange.from(21));
- addSupportedSigAlg(
- OID_DIGEST_SHA384, OID_SIG_SHA512_WITH_RSA,
- InclusiveIntRange.fromTo(21, 23));
-
- addSupportedSigAlg(
- OID_DIGEST_SHA512, OID_SIG_RSA,
- InclusiveIntRange.from(18));
- addSupportedSigAlg(
- OID_DIGEST_SHA512, OID_SIG_MD5_WITH_RSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA512, OID_SIG_SHA1_WITH_RSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA512, OID_SIG_SHA224_WITH_RSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA512, OID_SIG_SHA256_WITH_RSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA512, OID_SIG_SHA384_WITH_RSA,
- InclusiveIntRange.fromTo(21, 21));
- addSupportedSigAlg(
- OID_DIGEST_SHA512, OID_SIG_SHA512_WITH_RSA,
- InclusiveIntRange.from(21));
-
- addSupportedSigAlg(
- OID_DIGEST_MD5, OID_SIG_SHA1_WITH_DSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_MD5, OID_SIG_SHA224_WITH_DSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_MD5, OID_SIG_SHA256_WITH_DSA,
- InclusiveIntRange.fromTo(21, 23));
-
- addSupportedSigAlg(
- OID_DIGEST_SHA1, OID_SIG_DSA,
- InclusiveIntRange.from(0));
- addSupportedSigAlg(
- OID_DIGEST_SHA1, OID_SIG_SHA1_WITH_DSA,
- InclusiveIntRange.from(9));
- addSupportedSigAlg(
- OID_DIGEST_SHA1, OID_SIG_SHA224_WITH_DSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA1, OID_SIG_SHA256_WITH_DSA,
- InclusiveIntRange.fromTo(21, 23));
-
- addSupportedSigAlg(
- OID_DIGEST_SHA224, OID_SIG_DSA,
- InclusiveIntRange.from(22));
- addSupportedSigAlg(
- OID_DIGEST_SHA224, OID_SIG_SHA1_WITH_DSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA224, OID_SIG_SHA224_WITH_DSA,
- InclusiveIntRange.from(21));
- addSupportedSigAlg(
- OID_DIGEST_SHA224, OID_SIG_SHA256_WITH_DSA,
- InclusiveIntRange.fromTo(21, 23));
-
- addSupportedSigAlg(
- OID_DIGEST_SHA256, OID_SIG_DSA,
- InclusiveIntRange.from(22));
- addSupportedSigAlg(
- OID_DIGEST_SHA256, OID_SIG_SHA1_WITH_DSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA256, OID_SIG_SHA224_WITH_DSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA256, OID_SIG_SHA256_WITH_DSA,
- InclusiveIntRange.from(21));
-
- addSupportedSigAlg(
- OID_DIGEST_SHA384, OID_SIG_SHA1_WITH_DSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA384, OID_SIG_SHA224_WITH_DSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA384, OID_SIG_SHA256_WITH_DSA,
- InclusiveIntRange.fromTo(21, 23));
-
- addSupportedSigAlg(
- OID_DIGEST_SHA512, OID_SIG_SHA1_WITH_DSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA512, OID_SIG_SHA224_WITH_DSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA512, OID_SIG_SHA256_WITH_DSA,
- InclusiveIntRange.fromTo(21, 23));
-
-
- addSupportedSigAlg(
- OID_DIGEST_MD5, OID_SIG_SHA1_WITH_ECDSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_MD5, OID_SIG_SHA224_WITH_ECDSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_MD5, OID_SIG_SHA256_WITH_ECDSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_MD5, OID_SIG_SHA384_WITH_ECDSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_MD5, OID_SIG_SHA512_WITH_ECDSA,
- InclusiveIntRange.fromTo(21, 23));
-
- addSupportedSigAlg(
- OID_DIGEST_SHA1, OID_SIG_SHA1_WITH_ECDSA,
- InclusiveIntRange.from(18));
- addSupportedSigAlg(
- OID_DIGEST_SHA1, OID_SIG_SHA224_WITH_ECDSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA1, OID_SIG_SHA256_WITH_ECDSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA1, OID_SIG_SHA384_WITH_ECDSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA1, OID_SIG_SHA512_WITH_ECDSA,
- InclusiveIntRange.fromTo(21, 23));
-
- addSupportedSigAlg(
- OID_DIGEST_SHA224, OID_SIG_SHA1_WITH_ECDSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA224, OID_SIG_SHA224_WITH_ECDSA,
- InclusiveIntRange.from(21));
- addSupportedSigAlg(
- OID_DIGEST_SHA224, OID_SIG_SHA256_WITH_ECDSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA224, OID_SIG_SHA384_WITH_ECDSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA224, OID_SIG_SHA512_WITH_ECDSA,
- InclusiveIntRange.fromTo(21, 23));
-
- addSupportedSigAlg(
- OID_DIGEST_SHA256, OID_SIG_SHA1_WITH_ECDSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA256, OID_SIG_SHA224_WITH_ECDSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA256, OID_SIG_SHA256_WITH_ECDSA,
- InclusiveIntRange.from(21));
- addSupportedSigAlg(
- OID_DIGEST_SHA256, OID_SIG_SHA384_WITH_ECDSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA256, OID_SIG_SHA512_WITH_ECDSA,
- InclusiveIntRange.fromTo(21, 23));
-
- addSupportedSigAlg(
- OID_DIGEST_SHA384, OID_SIG_SHA1_WITH_ECDSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA384, OID_SIG_SHA224_WITH_ECDSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA384, OID_SIG_SHA256_WITH_ECDSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA384, OID_SIG_SHA384_WITH_ECDSA,
- InclusiveIntRange.from(21));
- addSupportedSigAlg(
- OID_DIGEST_SHA384, OID_SIG_SHA512_WITH_ECDSA,
- InclusiveIntRange.fromTo(21, 23));
-
- addSupportedSigAlg(
- OID_DIGEST_SHA512, OID_SIG_SHA1_WITH_ECDSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA512, OID_SIG_SHA224_WITH_ECDSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA512, OID_SIG_SHA256_WITH_ECDSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA512, OID_SIG_SHA384_WITH_ECDSA,
- InclusiveIntRange.fromTo(21, 23));
- addSupportedSigAlg(
- OID_DIGEST_SHA512, OID_SIG_SHA512_WITH_ECDSA,
- InclusiveIntRange.from(21));
- }
-
- private static void addSupportedSigAlg(
- String digestAlgorithmOid,
- String signatureAlgorithmOid,
- InclusiveIntRange... supportedApiLevels) {
- SUPPORTED_SIG_ALG_OIDS.put(
- digestAlgorithmOid + "with" + signatureAlgorithmOid,
- Arrays.asList(supportedApiLevels));
- }
-
- private List<InclusiveIntRange> getSigAlgSupportedApiLevels(
- String digestAlgorithmOid,
- String signatureAlgorithmOid) {
- List<InclusiveIntRange> result =
- SUPPORTED_SIG_ALG_OIDS.get(digestAlgorithmOid + "with" + signatureAlgorithmOid);
- return (result != null) ? result : Collections.emptyList();
- }
-
- public void verifySigFileAgainstManifest(
- byte[] manifestBytes,
- ManifestParser.Section manifestMainSection,
- Map<String, ManifestParser.Section> entryNameToManifestSection,
- Map<Integer, String> supportedApkSigSchemeNames,
- Set<Integer> foundApkSigSchemeIds,
- int minSdkVersion,
- int maxSdkVersion) throws NoSuchAlgorithmException {
- // Inspect the main section of the .SF file.
- ManifestParser sf = new ManifestParser(mSigFileBytes);
- ManifestParser.Section sfMainSection = sf.readSection();
- if (sfMainSection.getAttributeValue(Attributes.Name.SIGNATURE_VERSION) == null) {
- mResult.addError(
- Issue.JAR_SIG_MISSING_VERSION_ATTR_IN_SIG_FILE,
- mSignatureFileEntry.getName());
- setIgnored();
- return;
- }
-
- if (maxSdkVersion >= AndroidSdkVersion.N) {
- // Android N and newer rejects APKs whose .SF file says they were supposed to be
- // signed with APK Signature Scheme v2 (or newer) and yet no such signature was
- // found.
- checkForStrippedApkSignatures(
- sfMainSection, supportedApkSigSchemeNames, foundApkSigSchemeIds);
- if (mResult.containsErrors()) {
- return;
- }
- }
-
- boolean createdBySigntool = false;
- String createdBy = sfMainSection.getAttributeValue("Created-By");
- if (createdBy != null) {
- createdBySigntool = createdBy.indexOf("signtool") != -1;
- }
- boolean manifestDigestVerified =
- verifyManifestDigest(
- sfMainSection,
- createdBySigntool,
- manifestBytes,
- minSdkVersion,
- maxSdkVersion);
- if (!createdBySigntool) {
- verifyManifestMainSectionDigest(
- sfMainSection,
- manifestMainSection,
- manifestBytes,
- minSdkVersion,
- maxSdkVersion);
- }
- if (mResult.containsErrors()) {
- return;
- }
-
- // Inspect per-entry sections of .SF file. Technically, if the digest of JAR manifest
- // verifies, per-entry sections should be ignored. However, most Android platform
- // implementations require that such sections exist.
- List<ManifestParser.Section> sfSections = sf.readAllSections();
- Set<String> sfEntryNames = new HashSet<>(sfSections.size());
- int sfSectionNumber = 0;
- for (ManifestParser.Section sfSection : sfSections) {
- sfSectionNumber++;
- String entryName = sfSection.getName();
- if (entryName == null) {
- mResult.addError(
- Issue.JAR_SIG_UNNNAMED_SIG_FILE_SECTION,
- mSignatureFileEntry.getName(),
- sfSectionNumber);
- setIgnored();
- return;
- }
- if (!sfEntryNames.add(entryName)) {
- mResult.addError(
- Issue.JAR_SIG_DUPLICATE_SIG_FILE_SECTION,
- mSignatureFileEntry.getName(),
- entryName);
- setIgnored();
- return;
- }
- if (manifestDigestVerified) {
- // No need to verify this entry's corresponding JAR manifest entry because the
- // JAR manifest verifies in full.
- continue;
- }
- // Whole-file digest of JAR manifest hasn't been verified. Thus, we need to verify
- // the digest of the JAR manifest section corresponding to this .SF section.
- ManifestParser.Section manifestSection = entryNameToManifestSection.get(entryName);
- if (manifestSection == null) {
- mResult.addError(
- Issue.JAR_SIG_NO_ZIP_ENTRY_DIGEST_IN_SIG_FILE,
- entryName,
- mSignatureFileEntry.getName());
- setIgnored();
- continue;
- }
- verifyManifestIndividualSectionDigest(
- sfSection,
- createdBySigntool,
- manifestSection,
- manifestBytes,
- minSdkVersion,
- maxSdkVersion);
- }
- mSigFileEntryNames = sfEntryNames;
- }
-
-
- /**
- * Returns {@code true} if the whole-file digest of the manifest against the main section of
- * the .SF file.
- */
- private boolean verifyManifestDigest(
- ManifestParser.Section sfMainSection,
- boolean createdBySigntool,
- byte[] manifestBytes,
- int minSdkVersion,
- int maxSdkVersion) throws NoSuchAlgorithmException {
- Collection<NamedDigest> expectedDigests =
- getDigestsToVerify(
- sfMainSection,
- ((createdBySigntool) ? "-Digest" : "-Digest-Manifest"),
- minSdkVersion,
- maxSdkVersion);
- boolean digestFound = !expectedDigests.isEmpty();
- if (!digestFound) {
- mResult.addWarning(
- Issue.JAR_SIG_NO_MANIFEST_DIGEST_IN_SIG_FILE,
- mSignatureFileEntry.getName());
- return false;
- }
-
- boolean verified = true;
- for (NamedDigest expectedDigest : expectedDigests) {
- String jcaDigestAlgorithm = expectedDigest.jcaDigestAlgorithm;
- byte[] actual = digest(jcaDigestAlgorithm, manifestBytes);
- byte[] expected = expectedDigest.digest;
- if (!Arrays.equals(expected, actual)) {
- mResult.addWarning(
- Issue.JAR_SIG_ZIP_ENTRY_DIGEST_DID_NOT_VERIFY,
- V1SchemeSigner.MANIFEST_ENTRY_NAME,
- jcaDigestAlgorithm,
- mSignatureFileEntry.getName(),
- Base64.getEncoder().encodeToString(actual),
- Base64.getEncoder().encodeToString(expected));
- verified = false;
- }
- }
- return verified;
- }
-
- /**
- * Verifies the digest of the manifest's main section against the main section of the .SF
- * file.
- */
- private void verifyManifestMainSectionDigest(
- ManifestParser.Section sfMainSection,
- ManifestParser.Section manifestMainSection,
- byte[] manifestBytes,
- int minSdkVersion,
- int maxSdkVersion) throws NoSuchAlgorithmException {
- Collection<NamedDigest> expectedDigests =
- getDigestsToVerify(
- sfMainSection,
- "-Digest-Manifest-Main-Attributes",
- minSdkVersion,
- maxSdkVersion);
- if (expectedDigests.isEmpty()) {
- return;
- }
-
- for (NamedDigest expectedDigest : expectedDigests) {
- String jcaDigestAlgorithm = expectedDigest.jcaDigestAlgorithm;
- byte[] actual =
- digest(
- jcaDigestAlgorithm,
- manifestBytes,
- manifestMainSection.getStartOffset(),
- manifestMainSection.getSizeBytes());
- byte[] expected = expectedDigest.digest;
- if (!Arrays.equals(expected, actual)) {
- mResult.addError(
- Issue.JAR_SIG_MANIFEST_MAIN_SECTION_DIGEST_DID_NOT_VERIFY,
- jcaDigestAlgorithm,
- mSignatureFileEntry.getName(),
- Base64.getEncoder().encodeToString(actual),
- Base64.getEncoder().encodeToString(expected));
- }
- }
- }
-
- /**
- * Verifies the digest of the manifest's individual section against the corresponding
- * individual section of the .SF file.
- */
- private void verifyManifestIndividualSectionDigest(
- ManifestParser.Section sfIndividualSection,
- boolean createdBySigntool,
- ManifestParser.Section manifestIndividualSection,
- byte[] manifestBytes,
- int minSdkVersion,
- int maxSdkVersion) throws NoSuchAlgorithmException {
- String entryName = sfIndividualSection.getName();
- Collection<NamedDigest> expectedDigests =
- getDigestsToVerify(
- sfIndividualSection, "-Digest", minSdkVersion, maxSdkVersion);
- if (expectedDigests.isEmpty()) {
- mResult.addError(
- Issue.JAR_SIG_NO_ZIP_ENTRY_DIGEST_IN_SIG_FILE,
- entryName,
- mSignatureFileEntry.getName());
- return;
- }
-
- int sectionStartIndex = manifestIndividualSection.getStartOffset();
- int sectionSizeBytes = manifestIndividualSection.getSizeBytes();
- if (createdBySigntool) {
- int sectionEndIndex = sectionStartIndex + sectionSizeBytes;
- if ((manifestBytes[sectionEndIndex - 1] == '\n')
- && (manifestBytes[sectionEndIndex - 2] == '\n')) {
- sectionSizeBytes--;
- }
- }
- for (NamedDigest expectedDigest : expectedDigests) {
- String jcaDigestAlgorithm = expectedDigest.jcaDigestAlgorithm;
- byte[] actual =
- digest(
- jcaDigestAlgorithm,
- manifestBytes,
- sectionStartIndex,
- sectionSizeBytes);
- byte[] expected = expectedDigest.digest;
- if (!Arrays.equals(expected, actual)) {
- mResult.addError(
- Issue.JAR_SIG_MANIFEST_SECTION_DIGEST_DID_NOT_VERIFY,
- entryName,
- jcaDigestAlgorithm,
- mSignatureFileEntry.getName(),
- Base64.getEncoder().encodeToString(actual),
- Base64.getEncoder().encodeToString(expected));
- }
- }
- }
-
- private void checkForStrippedApkSignatures(
- ManifestParser.Section sfMainSection,
- Map<Integer, String> supportedApkSigSchemeNames,
- Set<Integer> foundApkSigSchemeIds) {
- String signedWithApkSchemes =
- sfMainSection.getAttributeValue(
- V1SchemeSigner.SF_ATTRIBUTE_NAME_ANDROID_APK_SIGNED_NAME_STR);
- // This field contains a comma-separated list of APK signature scheme IDs which were
- // used to sign this APK. Android rejects APKs where an ID is known to the platform but
- // the APK didn't verify using that scheme.
-
- if (signedWithApkSchemes == null) {
- // APK signature (e.g., v2 scheme) stripping protections not enabled.
- if (!foundApkSigSchemeIds.isEmpty()) {
- // APK is signed with an APK signature scheme such as v2 scheme.
- mResult.addWarning(
- Issue.JAR_SIG_NO_APK_SIG_STRIP_PROTECTION,
- mSignatureFileEntry.getName());
- }
- return;
- }
-
- if (supportedApkSigSchemeNames.isEmpty()) {
- return;
- }
-
- Set<Integer> supportedApkSigSchemeIds = supportedApkSigSchemeNames.keySet();
- Set<Integer> supportedExpectedApkSigSchemeIds = new HashSet<>(1);
- StringTokenizer tokenizer = new StringTokenizer(signedWithApkSchemes, ",");
- while (tokenizer.hasMoreTokens()) {
- String idText = tokenizer.nextToken().trim();
- if (idText.isEmpty()) {
- continue;
- }
- int id;
- try {
- id = Integer.parseInt(idText);
- } catch (Exception ignored) {
- continue;
- }
- // This APK was supposed to be signed with the APK signature scheme having
- // this ID.
- if (supportedApkSigSchemeIds.contains(id)) {
- supportedExpectedApkSigSchemeIds.add(id);
- } else {
- mResult.addWarning(
- Issue.JAR_SIG_UNKNOWN_APK_SIG_SCHEME_ID,
- mSignatureFileEntry.getName(),
- id);
- }
- }
-
- for (int id : supportedExpectedApkSigSchemeIds) {
- if (!foundApkSigSchemeIds.contains(id)) {
- String apkSigSchemeName = supportedApkSigSchemeNames.get(id);
- mResult.addError(
- Issue.JAR_SIG_MISSING_APK_SIG_REFERENCED,
- mSignatureFileEntry.getName(),
- id,
- apkSigSchemeName);
- }
- }
- }
- }
-
- private static Collection<NamedDigest> getDigestsToVerify(
- ManifestParser.Section section,
- String digestAttrSuffix,
- int minSdkVersion,
- int maxSdkVersion) {
- Decoder base64Decoder = Base64.getDecoder();
- List<NamedDigest> result = new ArrayList<>(1);
- if (minSdkVersion < AndroidSdkVersion.JELLY_BEAN_MR2) {
- // Prior to JB MR2, Android platform's logic for picking a digest algorithm to verify is
- // to rely on the ancient Digest-Algorithms attribute which contains
- // whitespace-separated list of digest algorithms (defaulting to SHA-1) to try. The
- // first digest attribute (with supported digest algorithm) found using the list is
- // used.
- String algs = section.getAttributeValue("Digest-Algorithms");
- if (algs == null) {
- algs = "SHA SHA1";
- }
- StringTokenizer tokens = new StringTokenizer(algs);
- while (tokens.hasMoreTokens()) {
- String alg = tokens.nextToken();
- String attrName = alg + digestAttrSuffix;
- String digestBase64 = section.getAttributeValue(attrName);
- if (digestBase64 == null) {
- // Attribute not found
- continue;
- }
- alg = getCanonicalJcaMessageDigestAlgorithm(alg);
- if ((alg == null)
- || (getMinSdkVersionFromWhichSupportedInManifestOrSignatureFile(alg)
- > minSdkVersion)) {
- // Unsupported digest algorithm
- continue;
- }
- // Supported digest algorithm
- result.add(new NamedDigest(alg, base64Decoder.decode(digestBase64)));
- break;
- }
- // No supported digests found -- this will fail to verify on pre-JB MR2 Androids.
- if (result.isEmpty()) {
- return result;
- }
- }
-
- if (maxSdkVersion >= AndroidSdkVersion.JELLY_BEAN_MR2) {
- // On JB MR2 and newer, Android platform picks the strongest algorithm out of:
- // SHA-512, SHA-384, SHA-256, SHA-1.
- for (String alg : JB_MR2_AND_NEWER_DIGEST_ALGS) {
- String attrName = getJarDigestAttributeName(alg, digestAttrSuffix);
- String digestBase64 = section.getAttributeValue(attrName);
- if (digestBase64 == null) {
- // Attribute not found
- continue;
- }
- byte[] digest = base64Decoder.decode(digestBase64);
- byte[] digestInResult = getDigest(result, alg);
- if ((digestInResult == null) || (!Arrays.equals(digestInResult, digest))) {
- result.add(new NamedDigest(alg, digest));
- }
- break;
- }
- }
-
- return result;
- }
-
- private static String[] JB_MR2_AND_NEWER_DIGEST_ALGS = {
- "SHA-512",
- "SHA-384",
- "SHA-256",
- "SHA-1",
- };
-
- private static String getCanonicalJcaMessageDigestAlgorithm(String algorithm) {
- return UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL.get(algorithm.toUpperCase(Locale.US));
- }
-
- public static int getMinSdkVersionFromWhichSupportedInManifestOrSignatureFile(
- String jcaAlgorithmName) {
- Integer result =
- MIN_SDK_VESION_FROM_WHICH_DIGEST_SUPPORTED_IN_MANIFEST.get(
- jcaAlgorithmName.toUpperCase(Locale.US));
- return (result != null) ? result : Integer.MAX_VALUE;
- }
-
- private static String getJarDigestAttributeName(
- String jcaDigestAlgorithm, String attrNameSuffix) {
- if ("SHA-1".equalsIgnoreCase(jcaDigestAlgorithm)) {
- return "SHA1" + attrNameSuffix;
- } else {
- return jcaDigestAlgorithm + attrNameSuffix;
- }
- }
-
- private static Map<String, String> UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL;
- static {
- UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL = new HashMap<>(8);
- UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL.put("MD5", "MD5");
- UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL.put("SHA", "SHA-1");
- UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL.put("SHA1", "SHA-1");
- UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL.put("SHA-1", "SHA-1");
- UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL.put("SHA-256", "SHA-256");
- UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL.put("SHA-384", "SHA-384");
- UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL.put("SHA-512", "SHA-512");
- }
-
- private static Map<String, Integer> MIN_SDK_VESION_FROM_WHICH_DIGEST_SUPPORTED_IN_MANIFEST;
- static {
- MIN_SDK_VESION_FROM_WHICH_DIGEST_SUPPORTED_IN_MANIFEST = new HashMap<>(5);
- MIN_SDK_VESION_FROM_WHICH_DIGEST_SUPPORTED_IN_MANIFEST.put("MD5", 0);
- MIN_SDK_VESION_FROM_WHICH_DIGEST_SUPPORTED_IN_MANIFEST.put("SHA-1", 0);
- MIN_SDK_VESION_FROM_WHICH_DIGEST_SUPPORTED_IN_MANIFEST.put("SHA-256", 0);
- MIN_SDK_VESION_FROM_WHICH_DIGEST_SUPPORTED_IN_MANIFEST.put(
- "SHA-384", AndroidSdkVersion.GINGERBREAD);
- MIN_SDK_VESION_FROM_WHICH_DIGEST_SUPPORTED_IN_MANIFEST.put(
- "SHA-512", AndroidSdkVersion.GINGERBREAD);
- }
-
- private static byte[] getDigest(Collection<NamedDigest> digests, String jcaDigestAlgorithm) {
- for (NamedDigest digest : digests) {
- if (digest.jcaDigestAlgorithm.equalsIgnoreCase(jcaDigestAlgorithm)) {
- return digest.digest;
- }
- }
- return null;
- }
-
- private static List<CentralDirectoryRecord> parseZipCentralDirectory(
- DataSource apk,
- ApkUtils.ZipSections apkSections)
- throws IOException, ZipFormatException {
- // Read the ZIP Central Directory
- long cdSizeBytes = apkSections.getZipCentralDirectorySizeBytes();
- if (cdSizeBytes > Integer.MAX_VALUE) {
- throw new ZipFormatException("ZIP Central Directory too large: " + cdSizeBytes);
- }
- long cdOffset = apkSections.getZipCentralDirectoryOffset();
- ByteBuffer cd = apk.getByteBuffer(cdOffset, (int) cdSizeBytes);
- cd.order(ByteOrder.LITTLE_ENDIAN);
-
- // Parse the ZIP Central Directory
- int expectedCdRecordCount = apkSections.getZipCentralDirectoryRecordCount();
- List<CentralDirectoryRecord> cdRecords = new ArrayList<>(expectedCdRecordCount);
- for (int i = 0; i < expectedCdRecordCount; i++) {
- CentralDirectoryRecord cdRecord;
- int offsetInsideCd = cd.position();
- try {
- cdRecord = CentralDirectoryRecord.getRecord(cd);
- } catch (ZipFormatException e) {
- throw new ZipFormatException(
- "Failed to parse Central Directory record #" + (i + 1)
- + " at file offset " + (cdOffset + offsetInsideCd),
- e);
- }
- String entryName = cdRecord.getName();
- if (entryName.endsWith("/")) {
- // Ignore directory entries
- continue;
- }
- cdRecords.add(cdRecord);
- }
- // There may be more data in Central Directory, but we don't warn or throw because Android
- // ignores unused CD data.
-
- return cdRecords;
- }
-
- /**
- * Returns {@code true} if the provided JAR entry must be mentioned in signed JAR archive's
- * manifest for the APK to verify on Android.
- */
- private static boolean isJarEntryDigestNeededInManifest(String entryName) {
- // NOTE: This logic is different from what's required by the JAR signing scheme. This is
- // because Android's APK verification logic differs from that spec. In particular, JAR
- // signing spec includes into JAR manifest all files in subdirectories of META-INF and
- // any files inside META-INF not related to signatures.
- if (entryName.startsWith("META-INF/")) {
- return false;
- }
- return !entryName.endsWith("/");
- }
-
- private static Set<Signer> verifyJarEntriesAgainstManifestAndSigners(
- DataSource apk,
- long cdOffsetInApk,
- Collection<CentralDirectoryRecord> cdRecords,
- Map<String, ManifestParser.Section> entryNameToManifestSection,
- List<Signer> signers,
- int minSdkVersion,
- int maxSdkVersion,
- Result result) throws ZipFormatException, IOException, NoSuchAlgorithmException {
- // Iterate over APK contents as sequentially as possible to improve performance.
- List<CentralDirectoryRecord> cdRecordsSortedByLocalFileHeaderOffset =
- new ArrayList<>(cdRecords);
- Collections.sort(
- cdRecordsSortedByLocalFileHeaderOffset,
- CentralDirectoryRecord.BY_LOCAL_FILE_HEADER_OFFSET_COMPARATOR);
- Set<String> manifestEntryNamesMissingFromApk =
- new HashSet<>(entryNameToManifestSection.keySet());
- List<Signer> firstSignedEntrySigners = null;
- String firstSignedEntryName = null;
- for (CentralDirectoryRecord cdRecord : cdRecordsSortedByLocalFileHeaderOffset) {
- String entryName = cdRecord.getName();
- manifestEntryNamesMissingFromApk.remove(entryName);
- if (!isJarEntryDigestNeededInManifest(entryName)) {
- continue;
- }
-
- ManifestParser.Section manifestSection = entryNameToManifestSection.get(entryName);
- if (manifestSection == null) {
- result.addError(Issue.JAR_SIG_NO_ZIP_ENTRY_DIGEST_IN_MANIFEST, entryName);
- continue;
- }
-
- List<Signer> entrySigners = new ArrayList<>(signers.size());
- for (Signer signer : signers) {
- if (signer.getSigFileEntryNames().contains(entryName)) {
- entrySigners.add(signer);
- }
- }
- if (entrySigners.isEmpty()) {
- result.addError(Issue.JAR_SIG_ZIP_ENTRY_NOT_SIGNED, entryName);
- continue;
- }
- if (firstSignedEntrySigners == null) {
- firstSignedEntrySigners = entrySigners;
- firstSignedEntryName = entryName;
- } else if (!entrySigners.equals(firstSignedEntrySigners)) {
- result.addError(
- Issue.JAR_SIG_ZIP_ENTRY_SIGNERS_MISMATCH,
- firstSignedEntryName,
- getSignerNames(firstSignedEntrySigners),
- entryName,
- getSignerNames(entrySigners));
- continue;
- }
-
- Collection<NamedDigest> expectedDigests =
- getDigestsToVerify(manifestSection, "-Digest", minSdkVersion, maxSdkVersion);
- if (expectedDigests.isEmpty()) {
- result.addError(Issue.JAR_SIG_NO_ZIP_ENTRY_DIGEST_IN_MANIFEST, entryName);
- continue;
- }
-
- MessageDigest[] mds = new MessageDigest[expectedDigests.size()];
- int mdIndex = 0;
- for (NamedDigest expectedDigest : expectedDigests) {
- mds[mdIndex] = getMessageDigest(expectedDigest.jcaDigestAlgorithm);
- mdIndex++;
- }
-
- try {
- LocalFileRecord.outputUncompressedData(
- apk,
- cdRecord,
- cdOffsetInApk,
- new MessageDigestSink(mds));
- } catch (ZipFormatException e) {
- throw new ZipFormatException("Malformed entry: " + entryName, e);
- } catch (IOException e) {
- throw new IOException("Failed to read entry: " + entryName, e);
- }
-
- mdIndex = 0;
- for (NamedDigest expectedDigest : expectedDigests) {
- byte[] actualDigest = mds[mdIndex].digest();
- if (!Arrays.equals(expectedDigest.digest, actualDigest)) {
- result.addError(
- Issue.JAR_SIG_ZIP_ENTRY_DIGEST_DID_NOT_VERIFY,
- entryName,
- expectedDigest.jcaDigestAlgorithm,
- V1SchemeSigner.MANIFEST_ENTRY_NAME,
- Base64.getEncoder().encodeToString(actualDigest),
- Base64.getEncoder().encodeToString(expectedDigest.digest));
- }
- }
- }
-
- if (firstSignedEntrySigners == null) {
- result.addError(Issue.JAR_SIG_NO_SIGNED_ZIP_ENTRIES);
- return Collections.emptySet();
- } else {
- return new HashSet<>(firstSignedEntrySigners);
- }
- }
-
- private static List<String> getSignerNames(List<Signer> signers) {
- if (signers.isEmpty()) {
- return Collections.emptyList();
- }
- List<String> result = new ArrayList<>(signers.size());
- for (Signer signer : signers) {
- result.add(signer.getName());
- }
- return result;
- }
-
- private static MessageDigest getMessageDigest(String algorithm)
- throws NoSuchAlgorithmException {
- return MessageDigest.getInstance(algorithm);
- }
-
- private static byte[] digest(String algorithm, byte[] data, int offset, int length)
- throws NoSuchAlgorithmException {
- MessageDigest md = getMessageDigest(algorithm);
- md.update(data, offset, length);
- return md.digest();
- }
-
- private static byte[] digest(String algorithm, byte[] data) throws NoSuchAlgorithmException {
- return getMessageDigest(algorithm).digest(data);
- }
-
- private static class NamedDigest {
- private final String jcaDigestAlgorithm;
- private final byte[] digest;
-
- private NamedDigest(String jcaDigestAlgorithm, byte[] digest) {
- this.jcaDigestAlgorithm = jcaDigestAlgorithm;
- this.digest = digest;
- }
- }
-
- public static class Result {
-
- /** Whether the APK's JAR signature verifies. */
- public boolean verified;
-
- /** List of APK's signers. These signers are used by Android. */
- public final List<SignerInfo> signers = new ArrayList<>();
-
- /**
- * Signers encountered in the APK but not included in the set of the APK's signers. These
- * signers are ignored by Android.
- */
- public final List<SignerInfo> ignoredSigners = new ArrayList<>();
-
- private final List<IssueWithParams> mWarnings = new ArrayList<>();
- private final List<IssueWithParams> mErrors = new ArrayList<>();
-
- private boolean containsErrors() {
- if (!mErrors.isEmpty()) {
- return true;
- }
- for (SignerInfo signer : signers) {
- if (signer.containsErrors()) {
- return true;
- }
- }
- return false;
- }
-
- private void addError(Issue msg, Object... parameters) {
- mErrors.add(new IssueWithParams(msg, parameters));
- }
-
- private void addWarning(Issue msg, Object... parameters) {
- mWarnings.add(new IssueWithParams(msg, parameters));
- }
-
- public List<IssueWithParams> getErrors() {
- return mErrors;
- }
-
- public List<IssueWithParams> getWarnings() {
- return mWarnings;
- }
-
- public static class SignerInfo {
- public final String name;
- public final String signatureFileName;
- public final String signatureBlockFileName;
- public final List<X509Certificate> certChain = new ArrayList<>();
-
- private final List<IssueWithParams> mWarnings = new ArrayList<>();
- private final List<IssueWithParams> mErrors = new ArrayList<>();
-
- private SignerInfo(
- String name, String signatureBlockFileName, String signatureFileName) {
- this.name = name;
- this.signatureBlockFileName = signatureBlockFileName;
- this.signatureFileName = signatureFileName;
- }
-
- private boolean containsErrors() {
- return !mErrors.isEmpty();
- }
-
- private void addError(Issue msg, Object... parameters) {
- mErrors.add(new IssueWithParams(msg, parameters));
- }
-
- private void addWarning(Issue msg, Object... parameters) {
- mWarnings.add(new IssueWithParams(msg, parameters));
- }
-
- public List<IssueWithParams> getErrors() {
- return mErrors;
- }
-
- public List<IssueWithParams> getWarnings() {
- return mWarnings;
- }
- }
- }
-}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/ContentDigestAlgorithm.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/ContentDigestAlgorithm.java
deleted file mode 100644
index 7c136bed3f..0000000000
--- a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/ContentDigestAlgorithm.java
+++ /dev/null
@@ -1,52 +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.apksigner.core.internal.apk.v2;
-
-/**
- * APK Signature Scheme v2 content digest algorithm.
- */
-public enum ContentDigestAlgorithm {
- /** SHA2-256 over 1 MB chunks. */
- CHUNKED_SHA256("SHA-256", 256 / 8),
-
- /** SHA2-512 over 1 MB chunks. */
- CHUNKED_SHA512("SHA-512", 512 / 8);
-
- private final String mJcaMessageDigestAlgorithm;
- private final int mChunkDigestOutputSizeBytes;
-
- private ContentDigestAlgorithm(
- String jcaMessageDigestAlgorithm, int chunkDigestOutputSizeBytes) {
- mJcaMessageDigestAlgorithm = jcaMessageDigestAlgorithm;
- mChunkDigestOutputSizeBytes = chunkDigestOutputSizeBytes;
- }
-
- /**
- * Returns the {@link java.security.MessageDigest} algorithm used for computing digests of
- * chunks by this content digest algorithm.
- */
- String getJcaMessageDigestAlgorithm() {
- return mJcaMessageDigestAlgorithm;
- }
-
- /**
- * Returns the size (in bytes) of the digest of a chunk of content.
- */
- int getChunkDigestOutputSizeBytes() {
- return mChunkDigestOutputSizeBytes;
- }
-} \ No newline at end of file
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/SignatureAlgorithm.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/SignatureAlgorithm.java
deleted file mode 100644
index 20f890d466..0000000000
--- a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/SignatureAlgorithm.java
+++ /dev/null
@@ -1,142 +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.apksigner.core.internal.apk.v2;
-
-import com.android.apksigner.core.internal.util.Pair;
-
-import java.security.spec.AlgorithmParameterSpec;
-import java.security.spec.MGF1ParameterSpec;
-import java.security.spec.PSSParameterSpec;
-
-/**
- * APK Signature Scheme v2 signature algorithm.
- */
-public enum SignatureAlgorithm {
- /**
- * RSASSA-PSS with SHA2-256 digest, SHA2-256 MGF1, 32 bytes of salt, trailer: 0xbc, content
- * digested using SHA2-256 in 1 MB chunks.
- */
- RSA_PSS_WITH_SHA256(
- 0x0101,
- ContentDigestAlgorithm.CHUNKED_SHA256,
- "RSA",
- Pair.of("SHA256withRSA/PSS",
- new PSSParameterSpec(
- "SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 256 / 8, 1))),
-
- /**
- * RSASSA-PSS with SHA2-512 digest, SHA2-512 MGF1, 64 bytes of salt, trailer: 0xbc, content
- * digested using SHA2-512 in 1 MB chunks.
- */
- RSA_PSS_WITH_SHA512(
- 0x0102,
- ContentDigestAlgorithm.CHUNKED_SHA512,
- "RSA",
- Pair.of(
- "SHA512withRSA/PSS",
- new PSSParameterSpec(
- "SHA-512", "MGF1", MGF1ParameterSpec.SHA512, 512 / 8, 1))),
-
- /** RSASSA-PKCS1-v1_5 with SHA2-256 digest, content digested using SHA2-256 in 1 MB chunks. */
- RSA_PKCS1_V1_5_WITH_SHA256(
- 0x0103,
- ContentDigestAlgorithm.CHUNKED_SHA256,
- "RSA",
- Pair.of("SHA256withRSA", null)),
-
- /** RSASSA-PKCS1-v1_5 with SHA2-512 digest, content digested using SHA2-512 in 1 MB chunks. */
- RSA_PKCS1_V1_5_WITH_SHA512(
- 0x0104,
- ContentDigestAlgorithm.CHUNKED_SHA512,
- "RSA",
- Pair.of("SHA512withRSA", null)),
-
- /** ECDSA with SHA2-256 digest, content digested using SHA2-256 in 1 MB chunks. */
- ECDSA_WITH_SHA256(
- 0x0201,
- ContentDigestAlgorithm.CHUNKED_SHA256,
- "EC",
- Pair.of("SHA256withECDSA", null)),
-
- /** ECDSA with SHA2-512 digest, content digested using SHA2-512 in 1 MB chunks. */
- ECDSA_WITH_SHA512(
- 0x0202,
- ContentDigestAlgorithm.CHUNKED_SHA512,
- "EC",
- Pair.of("SHA512withECDSA", null)),
-
- /** DSA with SHA2-256 digest, content digested using SHA2-256 in 1 MB chunks. */
- DSA_WITH_SHA256(
- 0x0301,
- ContentDigestAlgorithm.CHUNKED_SHA256,
- "DSA",
- Pair.of("SHA256withDSA", null));
-
- private final int mId;
- private final String mJcaKeyAlgorithm;
- private final ContentDigestAlgorithm mContentDigestAlgorithm;
- private final Pair<String, ? extends AlgorithmParameterSpec> mJcaSignatureAlgAndParams;
-
- private SignatureAlgorithm(int id,
- ContentDigestAlgorithm contentDigestAlgorithm,
- String jcaKeyAlgorithm,
- Pair<String, ? extends AlgorithmParameterSpec> jcaSignatureAlgAndParams) {
- mId = id;
- mContentDigestAlgorithm = contentDigestAlgorithm;
- mJcaKeyAlgorithm = jcaKeyAlgorithm;
- mJcaSignatureAlgAndParams = jcaSignatureAlgAndParams;
- }
-
- /**
- * Returns the ID of this signature algorithm as used in APK Signature Scheme v2 wire format.
- */
- int getId() {
- return mId;
- }
-
- /**
- * Returns the content digest algorithm associated with this signature algorithm.
- */
- ContentDigestAlgorithm getContentDigestAlgorithm() {
- return mContentDigestAlgorithm;
- }
-
- /**
- * Returns the JCA {@link java.security.Key} algorithm used by this signature scheme.
- */
- String getJcaKeyAlgorithm() {
- return mJcaKeyAlgorithm;
- }
-
- /**
- * Returns the {@link java.security.Signature} algorithm and the {@link AlgorithmParameterSpec}
- * (or null if not needed) to parameterize the {@code Signature}.
- */
- Pair<String, ? extends AlgorithmParameterSpec> getJcaSignatureAlgorithmAndParams() {
- return mJcaSignatureAlgAndParams;
- }
-
- static SignatureAlgorithm findById(int id) {
- for (SignatureAlgorithm alg : SignatureAlgorithm.values()) {
- if (alg.getId() == id) {
- return alg;
- }
- }
-
- return null;
- }
-}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/V2SchemeSigner.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/V2SchemeSigner.java
deleted file mode 100644
index 06d31dd1f6..0000000000
--- a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/V2SchemeSigner.java
+++ /dev/null
@@ -1,600 +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.apksigner.core.internal.apk.v2;
-
-import com.android.apksigner.core.internal.util.MessageDigestSink;
-import com.android.apksigner.core.internal.util.Pair;
-import com.android.apksigner.core.internal.zip.ZipUtils;
-import com.android.apksigner.core.util.DataSource;
-import com.android.apksigner.core.util.DataSources;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.security.DigestException;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.KeyFactory;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.Signature;
-import java.security.SignatureException;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.X509Certificate;
-import java.security.interfaces.ECKey;
-import java.security.interfaces.RSAKey;
-import java.security.spec.AlgorithmParameterSpec;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.X509EncodedKeySpec;
-import java.util.ArrayList;
-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 Signature Scheme v2 signer.
- *
- * <p>APK Signature Scheme v2 is a whole-file signature scheme which aims to protect every single
- * bit of the APK, as opposed to the JAR Signature Scheme which protects only the names and
- * uncompressed contents of ZIP entries.
- *
- * <p>TODO: Link to APK Signature Scheme v2 documentation once it's available.
- */
-public abstract class V2SchemeSigner {
- /*
- * The two main goals of APK Signature Scheme v2 are:
- * 1. Detect any unauthorized modifications to the APK. This is achieved by making the signature
- * cover every byte of the APK being signed.
- * 2. Enable much faster signature and integrity verification. This is achieved by requiring
- * only a minimal amount of APK parsing before the signature is verified, thus completely
- * bypassing ZIP entry decompression and by making integrity verification parallelizable by
- * employing a hash tree.
- *
- * The generated signature block is wrapped into an APK Signing Block and inserted into the
- * original APK immediately before the start of ZIP Central Directory. This is to ensure that
- * JAR and ZIP parsers continue to work on the signed APK. The APK Signing Block is designed for
- * extensibility. For example, a future signature scheme could insert its signatures there as
- * well. The contract of the APK Signing Block is that all contents outside of the block must be
- * protected by signatures inside the block.
- */
-
- private static final int CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES = 1024 * 1024;
-
- private static final byte[] APK_SIGNING_BLOCK_MAGIC =
- new byte[] {
- 0x41, 0x50, 0x4b, 0x20, 0x53, 0x69, 0x67, 0x20,
- 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x34, 0x32,
- };
- private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a;
-
- /**
- * Signer configuration.
- */
- public static class SignerConfig {
- /** Private key. */
- public PrivateKey privateKey;
-
- /**
- * Certificates, with the first certificate containing the public key corresponding to
- * {@link #privateKey}.
- */
- public List<X509Certificate> certificates;
-
- /**
- * List of signature algorithms with which to sign.
- */
- public List<SignatureAlgorithm> signatureAlgorithms;
- }
-
- /** Hidden constructor to prevent instantiation. */
- private V2SchemeSigner() {}
-
- /**
- * Gets the APK Signature Scheme v2 signature algorithms to be used for signing an APK using the
- * provided key.
- *
- * @param minSdkVersion minimum API Level of the platform on which the APK may be installed (see
- * AndroidManifest.xml minSdkVersion attribute).
- *
- * @throws InvalidKeyException if the provided key is not suitable for signing APKs using
- * APK Signature Scheme v2
- */
- public static List<SignatureAlgorithm> getSuggestedSignatureAlgorithms(
- PublicKey signingKey, int minSdkVersion) throws InvalidKeyException {
- String keyAlgorithm = signingKey.getAlgorithm();
- if ("RSA".equalsIgnoreCase(keyAlgorithm)) {
- // Use RSASSA-PKCS1-v1_5 signature scheme instead of RSASSA-PSS to guarantee
- // deterministic signatures which make life easier for OTA updates (fewer files
- // changed when deterministic signature schemes are used).
-
- // Pick a digest which is no weaker than the key.
- int modulusLengthBits = ((RSAKey) signingKey).getModulus().bitLength();
- if (modulusLengthBits <= 3072) {
- // 3072-bit RSA is roughly 128-bit strong, meaning SHA-256 is a good fit.
- return Collections.singletonList(SignatureAlgorithm.RSA_PKCS1_V1_5_WITH_SHA256);
- } else {
- // Keys longer than 3072 bit need to be paired with a stronger digest to avoid the
- // digest being the weak link. SHA-512 is the next strongest supported digest.
- return Collections.singletonList(SignatureAlgorithm.RSA_PKCS1_V1_5_WITH_SHA512);
- }
- } else if ("DSA".equalsIgnoreCase(keyAlgorithm)) {
- // DSA is supported only with SHA-256.
- return Collections.singletonList(SignatureAlgorithm.DSA_WITH_SHA256);
- } else if ("EC".equalsIgnoreCase(keyAlgorithm)) {
- // Pick a digest which is no weaker than the key.
- int keySizeBits = ((ECKey) signingKey).getParams().getOrder().bitLength();
- if (keySizeBits <= 256) {
- // 256-bit Elliptic Curve is roughly 128-bit strong, meaning SHA-256 is a good fit.
- return Collections.singletonList(SignatureAlgorithm.ECDSA_WITH_SHA256);
- } else {
- // Keys longer than 256 bit need to be paired with a stronger digest to avoid the
- // digest being the weak link. SHA-512 is the next strongest supported digest.
- return Collections.singletonList(SignatureAlgorithm.ECDSA_WITH_SHA512);
- }
- } else {
- throw new InvalidKeyException("Unsupported key algorithm: " + keyAlgorithm);
- }
- }
-
- /**
- * Signs the provided APK using APK Signature Scheme v2 and returns the APK Signing Block
- * containing the signature.
- *
- * @param signerConfigs signer configurations, one for each signer At least one signer config
- * must be provided.
- *
- * @throws IOException if an I/O error occurs
- * @throws NoSuchAlgorithmException if a required cryptographic algorithm implementation is
- * missing
- * @throws InvalidKeyException if a signing key is not suitable for this signature scheme or
- * cannot be used in general
- * @throws SignatureException if an error occurs when computing digests of generating
- * signatures
- */
- public static byte[] generateApkSigningBlock(
- DataSource beforeCentralDir,
- DataSource centralDir,
- DataSource eocd,
- List<SignerConfig> signerConfigs)
- throws IOException, NoSuchAlgorithmException, InvalidKeyException,
- SignatureException {
- if (signerConfigs.isEmpty()) {
- throw new IllegalArgumentException(
- "No signer configs provided. At least one is required");
- }
-
- // Figure out which digest(s) to use for APK contents.
- Set<ContentDigestAlgorithm> contentDigestAlgorithms = new HashSet<>(1);
- for (SignerConfig signerConfig : signerConfigs) {
- for (SignatureAlgorithm signatureAlgorithm : signerConfig.signatureAlgorithms) {
- contentDigestAlgorithms.add(signatureAlgorithm.getContentDigestAlgorithm());
- }
- }
-
- // Ensure that, when digesting, ZIP End of Central Directory record's Central Directory
- // offset field is treated as pointing to the offset at which the APK Signing Block will
- // start.
- long centralDirOffsetForDigesting = beforeCentralDir.size();
- ByteBuffer eocdBuf = ByteBuffer.allocate((int) eocd.size());
- eocdBuf.order(ByteOrder.LITTLE_ENDIAN);
- eocd.copyTo(0, (int) eocd.size(), eocdBuf);
- eocdBuf.flip();
- ZipUtils.setZipEocdCentralDirectoryOffset(eocdBuf, centralDirOffsetForDigesting);
-
- // Compute digests of APK contents.
- Map<ContentDigestAlgorithm, byte[]> contentDigests; // digest algorithm ID -> digest
- try {
- contentDigests =
- computeContentDigests(
- contentDigestAlgorithms,
- new DataSource[] {
- beforeCentralDir,
- centralDir,
- DataSources.asDataSource(eocdBuf)});
- } catch (IOException e) {
- throw new IOException("Failed to read APK being signed", e);
- } catch (DigestException e) {
- throw new SignatureException("Failed to compute digests of APK", e);
- }
-
- // Sign the digests and wrap the signatures and signer info into an APK Signing Block.
- return generateApkSigningBlock(signerConfigs, contentDigests);
- }
-
- static Map<ContentDigestAlgorithm, byte[]> computeContentDigests(
- Set<ContentDigestAlgorithm> digestAlgorithms,
- DataSource[] contents) throws IOException, NoSuchAlgorithmException, DigestException {
- // For each digest algorithm the result is computed as follows:
- // 1. Each segment of contents is split into consecutive chunks of 1 MB in size.
- // The final chunk will be shorter iff the length of segment is not a multiple of 1 MB.
- // No chunks are produced for empty (zero length) segments.
- // 2. The digest of each chunk is computed over the concatenation of byte 0xa5, the chunk's
- // length in bytes (uint32 little-endian) and the chunk's contents.
- // 3. The output digest is computed over the concatenation of the byte 0x5a, the number of
- // chunks (uint32 little-endian) and the concatenation of digests of chunks of all
- // segments in-order.
-
- long chunkCountLong = 0;
- for (DataSource input : contents) {
- chunkCountLong +=
- getChunkCount(input.size(), CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES);
- }
- if (chunkCountLong > Integer.MAX_VALUE) {
- throw new DigestException("Input too long: " + chunkCountLong + " chunks");
- }
- int chunkCount = (int) chunkCountLong;
-
- ContentDigestAlgorithm[] digestAlgorithmsArray =
- digestAlgorithms.toArray(new ContentDigestAlgorithm[digestAlgorithms.size()]);
- MessageDigest[] mds = new MessageDigest[digestAlgorithmsArray.length];
- byte[][] digestsOfChunks = new byte[digestAlgorithmsArray.length][];
- int[] digestOutputSizes = new int[digestAlgorithmsArray.length];
- for (int i = 0; i < digestAlgorithmsArray.length; i++) {
- ContentDigestAlgorithm digestAlgorithm = digestAlgorithmsArray[i];
- int digestOutputSizeBytes = digestAlgorithm.getChunkDigestOutputSizeBytes();
- digestOutputSizes[i] = digestOutputSizeBytes;
- byte[] concatenationOfChunkCountAndChunkDigests =
- new byte[5 + chunkCount * digestOutputSizeBytes];
- concatenationOfChunkCountAndChunkDigests[0] = 0x5a;
- setUnsignedInt32LittleEndian(
- chunkCount, concatenationOfChunkCountAndChunkDigests, 1);
- digestsOfChunks[i] = concatenationOfChunkCountAndChunkDigests;
- String jcaAlgorithm = digestAlgorithm.getJcaMessageDigestAlgorithm();
- mds[i] = MessageDigest.getInstance(jcaAlgorithm);
- }
-
- MessageDigestSink mdSink = new MessageDigestSink(mds);
- byte[] chunkContentPrefix = new byte[5];
- chunkContentPrefix[0] = (byte) 0xa5;
- int chunkIndex = 0;
- // Optimization opportunity: digests of chunks can be computed in parallel. However,
- // determining the number of computations to be performed in parallel is non-trivial. This
- // depends on a wide range of factors, such as data source type (e.g., in-memory or fetched
- // from file), CPU/memory/disk cache bandwidth and latency, interconnect architecture of CPU
- // cores, load on the system from other threads of execution and other processes, size of
- // input.
- // For now, we compute these digests sequentially and thus have the luxury of improving
- // performance by writing the digest of each chunk into a pre-allocated buffer at exactly
- // the right position. This avoids unnecessary allocations, copying, and enables the final
- // digest to be more efficient because it's presented with all of its input in one go.
- for (DataSource input : contents) {
- long inputOffset = 0;
- long inputRemaining = input.size();
- while (inputRemaining > 0) {
- int chunkSize =
- (int) Math.min(inputRemaining, CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES);
- setUnsignedInt32LittleEndian(chunkSize, chunkContentPrefix, 1);
- for (int i = 0; i < mds.length; i++) {
- mds[i].update(chunkContentPrefix);
- }
- try {
- input.feed(inputOffset, chunkSize, mdSink);
- } catch (IOException e) {
- throw new IOException("Failed to read chunk #" + chunkIndex, e);
- }
- for (int i = 0; i < digestAlgorithmsArray.length; i++) {
- MessageDigest md = mds[i];
- byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i];
- int expectedDigestSizeBytes = digestOutputSizes[i];
- int actualDigestSizeBytes =
- md.digest(
- concatenationOfChunkCountAndChunkDigests,
- 5 + chunkIndex * expectedDigestSizeBytes,
- expectedDigestSizeBytes);
- if (actualDigestSizeBytes != expectedDigestSizeBytes) {
- throw new RuntimeException(
- "Unexpected output size of " + md.getAlgorithm()
- + " digest: " + actualDigestSizeBytes);
- }
- }
- inputOffset += chunkSize;
- inputRemaining -= chunkSize;
- chunkIndex++;
- }
- }
-
- Map<ContentDigestAlgorithm, byte[]> result = new HashMap<>(digestAlgorithmsArray.length);
- for (int i = 0; i < digestAlgorithmsArray.length; i++) {
- ContentDigestAlgorithm digestAlgorithm = digestAlgorithmsArray[i];
- byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i];
- MessageDigest md = mds[i];
- byte[] digest = md.digest(concatenationOfChunkCountAndChunkDigests);
- result.put(digestAlgorithm, digest);
- }
- return result;
- }
-
- private static final long getChunkCount(long inputSize, int chunkSize) {
- return (inputSize + chunkSize - 1) / chunkSize;
- }
-
- private static void setUnsignedInt32LittleEndian(int value, byte[] result, int offset) {
- result[offset] = (byte) (value & 0xff);
- result[offset + 1] = (byte) ((value >> 8) & 0xff);
- result[offset + 2] = (byte) ((value >> 16) & 0xff);
- result[offset + 3] = (byte) ((value >> 24) & 0xff);
- }
-
- private static byte[] generateApkSigningBlock(
- List<SignerConfig> signerConfigs,
- Map<ContentDigestAlgorithm, byte[]> contentDigests)
- throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
- byte[] apkSignatureSchemeV2Block =
- generateApkSignatureSchemeV2Block(signerConfigs, contentDigests);
- return generateApkSigningBlock(apkSignatureSchemeV2Block);
- }
-
- private static byte[] generateApkSigningBlock(byte[] apkSignatureSchemeV2Block) {
- // FORMAT:
- // uint64: size (excluding this field)
- // repeated ID-value pairs:
- // uint64: size (excluding this field)
- // uint32: ID
- // (size - 4) bytes: value
- // uint64: size (same as the one above)
- // uint128: magic
-
- int resultSize =
- 8 // size
- + 8 + 4 + apkSignatureSchemeV2Block.length // v2Block as ID-value pair
- + 8 // size
- + 16 // magic
- ;
- ByteBuffer result = ByteBuffer.allocate(resultSize);
- result.order(ByteOrder.LITTLE_ENDIAN);
- long blockSizeFieldValue = resultSize - 8;
- result.putLong(blockSizeFieldValue);
-
- long pairSizeFieldValue = 4 + apkSignatureSchemeV2Block.length;
- result.putLong(pairSizeFieldValue);
- result.putInt(APK_SIGNATURE_SCHEME_V2_BLOCK_ID);
- result.put(apkSignatureSchemeV2Block);
-
- result.putLong(blockSizeFieldValue);
- result.put(APK_SIGNING_BLOCK_MAGIC);
-
- return result.array();
- }
-
- private static byte[] generateApkSignatureSchemeV2Block(
- List<SignerConfig> signerConfigs,
- Map<ContentDigestAlgorithm, byte[]> contentDigests)
- throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
- // FORMAT:
- // * length-prefixed sequence of length-prefixed signer blocks.
-
- List<byte[]> signerBlocks = new ArrayList<>(signerConfigs.size());
- int signerNumber = 0;
- for (SignerConfig signerConfig : signerConfigs) {
- signerNumber++;
- byte[] signerBlock;
- try {
- signerBlock = generateSignerBlock(signerConfig, contentDigests);
- } catch (InvalidKeyException e) {
- throw new InvalidKeyException("Signer #" + signerNumber + " failed", e);
- } catch (SignatureException e) {
- throw new SignatureException("Signer #" + signerNumber + " failed", e);
- }
- signerBlocks.add(signerBlock);
- }
-
- return encodeAsSequenceOfLengthPrefixedElements(
- new byte[][] {
- encodeAsSequenceOfLengthPrefixedElements(signerBlocks),
- });
- }
-
- private static byte[] generateSignerBlock(
- SignerConfig signerConfig,
- Map<ContentDigestAlgorithm, byte[]> contentDigests)
- throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
- if (signerConfig.certificates.isEmpty()) {
- throw new SignatureException("No certificates configured for signer");
- }
- PublicKey publicKey = signerConfig.certificates.get(0).getPublicKey();
-
- byte[] encodedPublicKey = encodePublicKey(publicKey);
-
- V2SignatureSchemeBlock.SignedData signedData = new V2SignatureSchemeBlock.SignedData();
- try {
- signedData.certificates = encodeCertificates(signerConfig.certificates);
- } catch (CertificateEncodingException e) {
- throw new SignatureException("Failed to encode certificates", e);
- }
-
- List<Pair<Integer, byte[]>> digests =
- new ArrayList<>(signerConfig.signatureAlgorithms.size());
- for (SignatureAlgorithm signatureAlgorithm : signerConfig.signatureAlgorithms) {
- ContentDigestAlgorithm contentDigestAlgorithm =
- signatureAlgorithm.getContentDigestAlgorithm();
- byte[] contentDigest = contentDigests.get(contentDigestAlgorithm);
- if (contentDigest == null) {
- throw new RuntimeException(
- contentDigestAlgorithm + " content digest for " + signatureAlgorithm
- + " not computed");
- }
- digests.add(Pair.of(signatureAlgorithm.getId(), contentDigest));
- }
- signedData.digests = digests;
-
- V2SignatureSchemeBlock.Signer signer = new V2SignatureSchemeBlock.Signer();
- // FORMAT:
- // * 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).
- // * length-prefixed sequence of length-prefixed additional attributes:
- // * uint32: ID
- // * (length - 4) bytes: value
- signer.signedData = encodeAsSequenceOfLengthPrefixedElements(new byte[][] {
- encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes(signedData.digests),
- encodeAsSequenceOfLengthPrefixedElements(signedData.certificates),
- // additional attributes
- new byte[0],
- });
- signer.publicKey = encodedPublicKey;
- signer.signatures = new ArrayList<>(signerConfig.signatureAlgorithms.size());
- for (SignatureAlgorithm signatureAlgorithm : signerConfig.signatureAlgorithms) {
- Pair<String, ? extends AlgorithmParameterSpec> sigAlgAndParams =
- signatureAlgorithm.getJcaSignatureAlgorithmAndParams();
- String jcaSignatureAlgorithm = sigAlgAndParams.getFirst();
- AlgorithmParameterSpec jcaSignatureAlgorithmParams = sigAlgAndParams.getSecond();
- byte[] signatureBytes;
- try {
- Signature signature = Signature.getInstance(jcaSignatureAlgorithm);
- signature.initSign(signerConfig.privateKey);
- if (jcaSignatureAlgorithmParams != null) {
- signature.setParameter(jcaSignatureAlgorithmParams);
- }
- signature.update(signer.signedData);
- signatureBytes = signature.sign();
- } catch (InvalidKeyException e) {
- throw new InvalidKeyException("Failed to sign using " + jcaSignatureAlgorithm, e);
- } catch (InvalidAlgorithmParameterException | SignatureException e) {
- throw new SignatureException("Failed to sign using " + jcaSignatureAlgorithm, e);
- }
-
- try {
- Signature signature = Signature.getInstance(jcaSignatureAlgorithm);
- signature.initVerify(publicKey);
- if (jcaSignatureAlgorithmParams != null) {
- signature.setParameter(jcaSignatureAlgorithmParams);
- }
- signature.update(signer.signedData);
- if (!signature.verify(signatureBytes)) {
- throw new SignatureException("Signature did not verify");
- }
- } catch (InvalidKeyException e) {
- throw new InvalidKeyException(
- "Failed to verify generated " + jcaSignatureAlgorithm + " signature using"
- + " public key from certificate", e);
- } catch (InvalidAlgorithmParameterException | SignatureException e) {
- throw new SignatureException(
- "Failed to verify generated " + jcaSignatureAlgorithm + " signature using"
- + " public key from certificate", e);
- }
-
- signer.signatures.add(Pair.of(signatureAlgorithm.getId(), signatureBytes));
- }
-
- // FORMAT:
- // * length-prefixed signed data
- // * length-prefixed sequence of length-prefixed signatures:
- // * uint32: signature algorithm ID
- // * length-prefixed bytes: signature of signed data
- // * length-prefixed bytes: public key (X.509 SubjectPublicKeyInfo, ASN.1 DER encoded)
- return encodeAsSequenceOfLengthPrefixedElements(
- new byte[][] {
- signer.signedData,
- encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes(
- signer.signatures),
- signer.publicKey,
- });
- }
-
- private static final class V2SignatureSchemeBlock {
- private static final class Signer {
- public byte[] signedData;
- public List<Pair<Integer, byte[]>> signatures;
- public byte[] publicKey;
- }
-
- private static final class SignedData {
- public List<Pair<Integer, byte[]>> digests;
- public List<byte[]> certificates;
- }
- }
-
- private static byte[] encodePublicKey(PublicKey publicKey)
- throws InvalidKeyException, NoSuchAlgorithmException {
- byte[] encodedPublicKey = null;
- if ("X.509".equals(publicKey.getFormat())) {
- encodedPublicKey = publicKey.getEncoded();
- }
- if (encodedPublicKey == null) {
- try {
- encodedPublicKey =
- KeyFactory.getInstance(publicKey.getAlgorithm())
- .getKeySpec(publicKey, X509EncodedKeySpec.class)
- .getEncoded();
- } catch (InvalidKeySpecException e) {
- throw new InvalidKeyException(
- "Failed to obtain X.509 encoded form of public key " + publicKey
- + " of class " + publicKey.getClass().getName(),
- e);
- }
- }
- if ((encodedPublicKey == null) || (encodedPublicKey.length == 0)) {
- throw new InvalidKeyException(
- "Failed to obtain X.509 encoded form of public key " + publicKey
- + " of class " + publicKey.getClass().getName());
- }
- return encodedPublicKey;
- }
-
- private static List<byte[]> encodeCertificates(List<X509Certificate> certificates)
- throws CertificateEncodingException {
- List<byte[]> result = new ArrayList<>(certificates.size());
- for (X509Certificate certificate : certificates) {
- result.add(certificate.getEncoded());
- }
- return result;
- }
-
- private static byte[] encodeAsSequenceOfLengthPrefixedElements(List<byte[]> sequence) {
- return encodeAsSequenceOfLengthPrefixedElements(
- sequence.toArray(new byte[sequence.size()][]));
- }
-
- private static byte[] encodeAsSequenceOfLengthPrefixedElements(byte[][] sequence) {
- int payloadSize = 0;
- for (byte[] element : sequence) {
- payloadSize += 4 + element.length;
- }
- ByteBuffer result = ByteBuffer.allocate(payloadSize);
- result.order(ByteOrder.LITTLE_ENDIAN);
- for (byte[] element : sequence) {
- result.putInt(element.length);
- result.put(element);
- }
- return result.array();
- }
-
- private static byte[] encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes(
- List<Pair<Integer, byte[]>> sequence) {
- int resultSize = 0;
- for (Pair<Integer, byte[]> element : sequence) {
- resultSize += 12 + element.getSecond().length;
- }
- ByteBuffer result = ByteBuffer.allocate(resultSize);
- result.order(ByteOrder.LITTLE_ENDIAN);
- for (Pair<Integer, byte[]> element : sequence) {
- byte[] second = element.getSecond();
- result.putInt(8 + second.length);
- result.putInt(element.getFirst());
- result.putInt(second.length);
- result.put(second);
- }
- return result.array();
- }
-}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/V2SchemeVerifier.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/V2SchemeVerifier.java
deleted file mode 100644
index 5e1e8fb743..0000000000
--- a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/V2SchemeVerifier.java
+++ /dev/null
@@ -1,955 +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.apksigner.core.internal.apk.v2;
-
-import com.android.apksigner.core.ApkVerifier.Issue;
-import com.android.apksigner.core.ApkVerifier.IssueWithParams;
-import com.android.apksigner.core.apk.ApkUtils;
-import com.android.apksigner.core.internal.util.ByteBufferDataSource;
-import com.android.apksigner.core.internal.util.DelegatingX509Certificate;
-import com.android.apksigner.core.internal.util.Pair;
-import com.android.apksigner.core.internal.zip.ZipUtils;
-import com.android.apksigner.core.util.DataSource;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.nio.BufferUnderflowException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.security.DigestException;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.KeyFactory;
-import java.security.NoSuchAlgorithmException;
-import java.security.PublicKey;
-import java.security.Signature;
-import java.security.SignatureException;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.security.spec.AlgorithmParameterSpec;
-import java.security.spec.X509EncodedKeySpec;
-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 Signature Scheme v2 verifier.
- *
- * <p>APK Signature Scheme v2 is a whole-file signature scheme which aims to protect every single
- * bit of the APK, as opposed to the JAR Signature Scheme which protects only the names and
- * uncompressed contents of ZIP entries.
- *
- * <p>TODO: Link to APK Signature Scheme v2 documentation once it's available.
- */
-public abstract class V2SchemeVerifier {
-
- private static final long APK_SIG_BLOCK_MAGIC_HI = 0x3234206b636f6c42L;
- private static final long APK_SIG_BLOCK_MAGIC_LO = 0x20676953204b5041L;
- private static final int APK_SIG_BLOCK_MIN_SIZE = 32;
-
- private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a;
-
- /** Hidden constructor to prevent instantiation. */
- private V2SchemeVerifier() {}
-
- /**
- * Verifies the provided APK's APK Signature Scheme v2 signatures and returns the result of
- * verification. APK is considered verified only if {@link Result#verified} is {@code true}. If
- * verification fails, the result will contain errors -- see {@link Result#getErrors()}.
- *
- * @throws NoSuchAlgorithmException if the APK's signatures cannot be verified because a
- * required cryptographic algorithm implementation is missing
- * @throws SignatureNotFoundException if no APK Signature Scheme v2 signatures are found
- * @throws IOException if an I/O error occurs when reading the APK
- */
- public static Result verify(DataSource apk, ApkUtils.ZipSections zipSections)
- throws IOException, NoSuchAlgorithmException, SignatureNotFoundException {
- Result result = new Result();
- SignatureInfo signatureInfo = findSignature(apk, zipSections, result);
-
- DataSource beforeApkSigningBlock = apk.slice(0, signatureInfo.apkSigningBlockOffset);
- DataSource centralDir =
- apk.slice(
- signatureInfo.centralDirOffset,
- signatureInfo.eocdOffset - signatureInfo.centralDirOffset);
- ByteBuffer eocd = signatureInfo.eocd;
-
- verify(beforeApkSigningBlock,
- signatureInfo.signatureBlock,
- centralDir,
- eocd,
- result);
- return result;
- }
-
- /**
- * Verifies the provided APK's v2 signatures and outputs the results into the provided
- * {@code result}. APK is considered verified only if there are no errors reported in the
- * {@code result}.
- */
- private static void verify(
- DataSource beforeApkSigningBlock,
- ByteBuffer apkSignatureSchemeV2Block,
- DataSource centralDir,
- ByteBuffer eocd,
- Result result) throws IOException, NoSuchAlgorithmException {
- Set<ContentDigestAlgorithm> contentDigestsToVerify = new HashSet<>(1);
- parseSigners(apkSignatureSchemeV2Block, contentDigestsToVerify, result);
- if (result.containsErrors()) {
- return;
- }
- verifyIntegrity(
- beforeApkSigningBlock, centralDir, eocd, contentDigestsToVerify, result);
- if (!result.containsErrors()) {
- result.verified = true;
- }
- }
-
- /**
- * Parses each signer in the provided APK Signature Scheme v2 block and populates
- * {@code signerInfos} of the provided {@code result}.
- *
- * <p>This verifies signatures over {@code signed-data} block contained in each signer block.
- * However, this does not verify the integrity of the rest of the APK but rather simply reports
- * the expected digests of the rest of the APK (see {@code contentDigestsToVerify}).
- */
- private static void parseSigners(
- ByteBuffer apkSignatureSchemeV2Block,
- Set<ContentDigestAlgorithm> contentDigestsToVerify,
- Result result) throws NoSuchAlgorithmException {
- ByteBuffer signers;
- try {
- signers = getLengthPrefixedSlice(apkSignatureSchemeV2Block);
- } catch (IOException e) {
- result.addError(Issue.V2_SIG_MALFORMED_SIGNERS);
- return;
- }
- if (!signers.hasRemaining()) {
- result.addError(Issue.V2_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);
- }
- int signerCount = 0;
- while (signers.hasRemaining()) {
- int signerIndex = signerCount;
- signerCount++;
- Result.SignerInfo signerInfo = new Result.SignerInfo();
- signerInfo.index = signerIndex;
- result.signers.add(signerInfo);
- try {
- ByteBuffer signer = getLengthPrefixedSlice(signers);
- parseSigner(signer, certFactory, signerInfo, contentDigestsToVerify);
- } catch (IOException | BufferUnderflowException e) {
- signerInfo.addError(Issue.V2_SIG_MALFORMED_SIGNER);
- return;
- }
- }
- }
-
- /**
- * Parses the provided signer block and populates the {@code result}.
- *
- * <p>This verifies signatures over {@code signed-data} contained in this block but does not
- * verify the integrity of the rest of the APK. Rather, this method adds to the
- * {@code contentDigestsToVerify}.
- */
- private static void parseSigner(
- ByteBuffer signerBlock,
- CertificateFactory certFactory,
- Result.SignerInfo result,
- Set<ContentDigestAlgorithm> contentDigestsToVerify)
- throws IOException, NoSuchAlgorithmException {
- ByteBuffer signedData = getLengthPrefixedSlice(signerBlock);
- byte[] signedDataBytes = new byte[signedData.remaining()];
- signedData.get(signedDataBytes);
- signedData.flip();
- result.signedData = signedDataBytes;
-
- ByteBuffer signatures = getLengthPrefixedSlice(signerBlock);
- byte[] publicKeyBytes = readLengthPrefixedByteArray(signerBlock);
-
- // Parse the signatures block and identify supported signatures
- int signatureCount = 0;
- List<SupportedSignature> supportedSignatures = new ArrayList<>(1);
- while (signatures.hasRemaining()) {
- signatureCount++;
- try {
- ByteBuffer signature = getLengthPrefixedSlice(signatures);
- int sigAlgorithmId = signature.getInt();
- byte[] sigBytes = readLengthPrefixedByteArray(signature);
- result.signatures.add(
- new Result.SignerInfo.Signature(sigAlgorithmId, sigBytes));
- SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.findById(sigAlgorithmId);
- if (signatureAlgorithm == null) {
- result.addWarning(Issue.V2_SIG_UNKNOWN_SIG_ALGORITHM, sigAlgorithmId);
- continue;
- }
- supportedSignatures.add(new SupportedSignature(signatureAlgorithm, sigBytes));
- } catch (IOException | BufferUnderflowException e) {
- result.addError(Issue.V2_SIG_MALFORMED_SIGNATURE, signatureCount);
- return;
- }
- }
- if (result.signatures.isEmpty()) {
- result.addError(Issue.V2_SIG_NO_SIGNATURES);
- return;
- }
-
- // Verify signatures over signed-data block using the public key
- List<SupportedSignature> signaturesToVerify = getSignaturesToVerify(supportedSignatures);
- if (signaturesToVerify.isEmpty()) {
- result.addError(Issue.V2_SIG_NO_SUPPORTED_SIGNATURES);
- return;
- }
- for (SupportedSignature signature : signaturesToVerify) {
- SignatureAlgorithm signatureAlgorithm = signature.algorithm;
- String jcaSignatureAlgorithm =
- signatureAlgorithm.getJcaSignatureAlgorithmAndParams().getFirst();
- AlgorithmParameterSpec jcaSignatureAlgorithmParams =
- signatureAlgorithm.getJcaSignatureAlgorithmAndParams().getSecond();
- String keyAlgorithm = signatureAlgorithm.getJcaKeyAlgorithm();
- PublicKey publicKey;
- try {
- publicKey =
- KeyFactory.getInstance(keyAlgorithm).generatePublic(
- new X509EncodedKeySpec(publicKeyBytes));
- } catch (Exception e) {
- result.addError(Issue.V2_SIG_MALFORMED_PUBLIC_KEY, e);
- return;
- }
- try {
- Signature sig = Signature.getInstance(jcaSignatureAlgorithm);
- sig.initVerify(publicKey);
- if (jcaSignatureAlgorithmParams != null) {
- sig.setParameter(jcaSignatureAlgorithmParams);
- }
- signedData.position(0);
- sig.update(signedData);
- byte[] sigBytes = signature.signature;
- if (!sig.verify(sigBytes)) {
- result.addError(Issue.V2_SIG_DID_NOT_VERIFY, signatureAlgorithm);
- return;
- }
- result.verifiedSignatures.put(signatureAlgorithm, sigBytes);
- contentDigestsToVerify.add(signatureAlgorithm.getContentDigestAlgorithm());
- } catch (InvalidKeyException | InvalidAlgorithmParameterException
- | SignatureException e) {
- result.addError(Issue.V2_SIG_VERIFY_EXCEPTION, signatureAlgorithm, e);
- return;
- }
- }
-
- // At least one signature over signedData has verified. We can now parse signed-data.
- signedData.position(0);
- ByteBuffer digests = getLengthPrefixedSlice(signedData);
- ByteBuffer certificates = getLengthPrefixedSlice(signedData);
- ByteBuffer additionalAttributes = getLengthPrefixedSlice(signedData);
-
- // Parse the certificates block
- int certificateIndex = -1;
- while (certificates.hasRemaining()) {
- certificateIndex++;
- byte[] encodedCert = readLengthPrefixedByteArray(certificates);
- X509Certificate certificate;
- try {
- certificate =
- (X509Certificate)
- certFactory.generateCertificate(
- new ByteArrayInputStream(encodedCert));
- } catch (CertificateException e) {
- result.addError(
- Issue.V2_SIG_MALFORMED_CERTIFICATE,
- certificateIndex,
- certificateIndex + 1,
- e);
- 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 becase some X509Certificate(Factory) implementations re-encode
- // certificates.
- certificate = new GuaranteedEncodedFormX509Certificate(certificate, encodedCert);
- result.certs.add(certificate);
- }
-
- if (result.certs.isEmpty()) {
- result.addError(Issue.V2_SIG_NO_CERTIFICATES);
- return;
- }
- X509Certificate mainCertificate = result.certs.get(0);
- byte[] certificatePublicKeyBytes = mainCertificate.getPublicKey().getEncoded();
- if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) {
- result.addError(
- Issue.V2_SIG_PUBLIC_KEY_MISMATCH_BETWEEN_CERTIFICATE_AND_SIGNATURES_RECORD,
- toHex(certificatePublicKeyBytes),
- toHex(publicKeyBytes));
- return;
- }
-
- // Parse the digests block
- int digestCount = 0;
- while (digests.hasRemaining()) {
- digestCount++;
- try {
- ByteBuffer digest = getLengthPrefixedSlice(digests);
- int sigAlgorithmId = digest.getInt();
- byte[] digestBytes = readLengthPrefixedByteArray(digest);
- result.contentDigests.add(
- new Result.SignerInfo.ContentDigest(sigAlgorithmId, digestBytes));
- } catch (IOException | BufferUnderflowException e) {
- result.addError(Issue.V2_SIG_MALFORMED_DIGEST, digestCount);
- return;
- }
- }
-
- List<Integer> sigAlgsFromSignaturesRecord = new ArrayList<>(result.signatures.size());
- for (Result.SignerInfo.Signature signature : result.signatures) {
- sigAlgsFromSignaturesRecord.add(signature.getAlgorithmId());
- }
- List<Integer> sigAlgsFromDigestsRecord = new ArrayList<>(result.contentDigests.size());
- for (Result.SignerInfo.ContentDigest digest : result.contentDigests) {
- sigAlgsFromDigestsRecord.add(digest.getSignatureAlgorithmId());
- }
-
- if (!sigAlgsFromSignaturesRecord.equals(sigAlgsFromDigestsRecord)) {
- result.addError(
- Issue.V2_SIG_SIG_ALG_MISMATCH_BETWEEN_SIGNATURES_AND_DIGESTS_RECORDS,
- sigAlgsFromSignaturesRecord,
- sigAlgsFromDigestsRecord);
- return;
- }
-
- // Parse the additional attributes block.
- int additionalAttributeCount = 0;
- while (additionalAttributes.hasRemaining()) {
- additionalAttributeCount++;
- try {
- ByteBuffer attribute = getLengthPrefixedSlice(additionalAttributes);
- int id = attribute.getInt();
- byte[] value = readLengthPrefixedByteArray(attribute);
- result.additionalAttributes.add(
- new Result.SignerInfo.AdditionalAttribute(id, value));
- result.addWarning(Issue.V2_SIG_UNKNOWN_ADDITIONAL_ATTRIBUTE, id);
- } catch (IOException | BufferUnderflowException e) {
- result.addError(
- Issue.V2_SIG_MALFORMED_ADDITIONAL_ATTRIBUTE, additionalAttributeCount);
- return;
- }
- }
- }
-
- private static List<SupportedSignature> getSignaturesToVerify(
- List<SupportedSignature> signatures) {
- // Pick the signature with the strongest algorithm, to mimic Android's behavior.
- SignatureAlgorithm bestSigAlgorithm = null;
- byte[] bestSigAlgorithmSignatureBytes = null;
- for (SupportedSignature sig : signatures) {
- SignatureAlgorithm sigAlgorithm = sig.algorithm;
- if ((bestSigAlgorithm == null)
- || (compareSignatureAlgorithm(sigAlgorithm, bestSigAlgorithm) > 0)) {
- bestSigAlgorithm = sigAlgorithm;
- bestSigAlgorithmSignatureBytes = sig.signature;
- }
- }
-
- if (bestSigAlgorithm == null) {
- return Collections.emptyList();
- } else {
- return Collections.singletonList(
- new SupportedSignature(bestSigAlgorithm, bestSigAlgorithmSignatureBytes));
- }
- }
-
- private static class SupportedSignature {
- private final SignatureAlgorithm algorithm;
- private final byte[] signature;
-
- private SupportedSignature(SignatureAlgorithm algorithm, byte[] signature) {
- this.algorithm = algorithm;
- this.signature = signature;
- }
- }
-
- /**
- * Returns positive number if {@code alg1} is preferred over {@code alg2}, {@code -1} if
- * {@code alg2} is preferred over {@code alg1}, and {@code 0} if there is no preference.
- */
- private static int compareSignatureAlgorithm(SignatureAlgorithm alg1, SignatureAlgorithm alg2) {
- ContentDigestAlgorithm digestAlg1 = alg1.getContentDigestAlgorithm();
- ContentDigestAlgorithm digestAlg2 = alg2.getContentDigestAlgorithm();
- return compareContentDigestAlgorithm(digestAlg1, digestAlg2);
- }
-
- /**
- * Returns positive number if {@code alg1} is preferred over {@code alg2}, {@code -1} if
- * {@code alg2} is preferred over {@code alg1}, and {@code 0} if there is no preference.
- */
- private static int compareContentDigestAlgorithm(
- ContentDigestAlgorithm alg1,
- ContentDigestAlgorithm alg2) {
- switch (alg1) {
- case CHUNKED_SHA256:
- switch (alg2) {
- case CHUNKED_SHA256:
- return 0;
- case CHUNKED_SHA512:
- return -1;
- default:
- throw new IllegalArgumentException("Unknown alg2: " + alg2);
- }
- case CHUNKED_SHA512:
- switch (alg2) {
- case CHUNKED_SHA256:
- return 1;
- case CHUNKED_SHA512:
- return 0;
- default:
- throw new IllegalArgumentException("Unknown alg2: " + alg2);
- }
- default:
- throw new IllegalArgumentException("Unknown alg1: " + alg1);
- }
- }
-
- /**
- * Verifies integrity of the APK outside of the APK Signing Block by computing digests of the
- * APK and comparing them against the digests listed in APK Signing Block. The expected digests
- * taken from {@code v2SchemeSignerInfos} of the provided {@code result}.
- */
- private static void verifyIntegrity(
- DataSource beforeApkSigningBlock,
- DataSource centralDir,
- ByteBuffer eocd,
- Set<ContentDigestAlgorithm> contentDigestAlgorithms,
- Result result) throws IOException, NoSuchAlgorithmException {
- if (contentDigestAlgorithms.isEmpty()) {
- // This should never occur because this method is invoked once at least one signature
- // is verified, meaning at least one content digest is known.
- throw new RuntimeException("No content digests found");
- }
-
- // For the purposes of verifying integrity, ZIP End of Central Directory (EoCD) must be
- // treated as though its Central Directory offset points to the start of APK Signing Block.
- // We thus modify the EoCD accordingly.
- ByteBuffer modifiedEocd = ByteBuffer.allocate(eocd.remaining());
- modifiedEocd.order(ByteOrder.LITTLE_ENDIAN);
- modifiedEocd.put(eocd);
- modifiedEocd.flip();
- ZipUtils.setZipEocdCentralDirectoryOffset(modifiedEocd, beforeApkSigningBlock.size());
- Map<ContentDigestAlgorithm, byte[]> actualContentDigests;
- try {
- actualContentDigests =
- V2SchemeSigner.computeContentDigests(
- contentDigestAlgorithms,
- new DataSource[] {
- beforeApkSigningBlock,
- centralDir,
- new ByteBufferDataSource(modifiedEocd)
- });
- } catch (DigestException e) {
- throw new RuntimeException("Failed to compute content digests", e);
- }
- if (!contentDigestAlgorithms.equals(actualContentDigests.keySet())) {
- throw new RuntimeException(
- "Mismatch between sets of requested and computed content digests"
- + " . Requested: " + contentDigestAlgorithms
- + ", computed: " + actualContentDigests.keySet());
- }
-
- // Compare digests computed over the rest of APK against the corresponding expected digests
- // in signer blocks.
- for (Result.SignerInfo signerInfo : result.signers) {
- for (Result.SignerInfo.ContentDigest expected : signerInfo.contentDigests) {
- SignatureAlgorithm signatureAlgorithm =
- SignatureAlgorithm.findById(expected.getSignatureAlgorithmId());
- if (signatureAlgorithm == null) {
- continue;
- }
- ContentDigestAlgorithm contentDigestAlgorithm =
- signatureAlgorithm.getContentDigestAlgorithm();
- byte[] expectedDigest = expected.getValue();
- byte[] actualDigest = actualContentDigests.get(contentDigestAlgorithm);
- if (!Arrays.equals(expectedDigest, actualDigest)) {
- signerInfo.addError(
- Issue.V2_SIG_APK_DIGEST_DID_NOT_VERIFY,
- contentDigestAlgorithm,
- toHex(expectedDigest),
- toHex(actualDigest));
- continue;
- }
- signerInfo.verifiedContentDigests.put(contentDigestAlgorithm, actualDigest);
- }
- }
- }
-
- /**
- * APK Signature Scheme v2 block and additional information relevant to verifying the signatures
- * contained in the block against the file.
- */
- private static class SignatureInfo {
- /** Contents of APK Signature Scheme v2 block. */
- private final ByteBuffer signatureBlock;
-
- /** Position of the APK Signing Block in the file. */
- private final long apkSigningBlockOffset;
-
- /** Position of the ZIP Central Directory in the file. */
- private final long centralDirOffset;
-
- /** Position of the ZIP End of Central Directory (EoCD) in the file. */
- private final long eocdOffset;
-
- /** Contents of ZIP End of Central Directory (EoCD) of the file. */
- private final ByteBuffer eocd;
-
- private SignatureInfo(
- ByteBuffer signatureBlock,
- long apkSigningBlockOffset,
- long centralDirOffset,
- long eocdOffset,
- ByteBuffer eocd) {
- this.signatureBlock = signatureBlock;
- this.apkSigningBlockOffset = apkSigningBlockOffset;
- this.centralDirOffset = centralDirOffset;
- this.eocdOffset = eocdOffset;
- this.eocd = eocd;
- }
- }
-
- /**
- * Returns the APK Signature Scheme v2 block contained in the provided APK file and the
- * additional information relevant for verifying the block against the file.
- *
- * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2
- * @throws IOException if an I/O error occurs while reading the APK
- */
- private static SignatureInfo findSignature(
- DataSource apk, ApkUtils.ZipSections zipSections, Result result)
- throws IOException, SignatureNotFoundException {
- // Find the APK Signing Block. The block immediately precedes the Central Directory.
- ByteBuffer eocd = zipSections.getZipEndOfCentralDirectory();
- Pair<DataSource, Long> apkSigningBlockAndOffset = findApkSigningBlock(apk, zipSections);
- DataSource apkSigningBlock = apkSigningBlockAndOffset.getFirst();
- long apkSigningBlockOffset = apkSigningBlockAndOffset.getSecond();
- ByteBuffer apkSigningBlockBuf =
- apkSigningBlock.getByteBuffer(0, (int) apkSigningBlock.size());
- apkSigningBlockBuf.order(ByteOrder.LITTLE_ENDIAN);
-
- // Find the APK Signature Scheme v2 Block inside the APK Signing Block.
- ByteBuffer apkSignatureSchemeV2Block =
- findApkSignatureSchemeV2Block(apkSigningBlockBuf, result);
-
- return new SignatureInfo(
- apkSignatureSchemeV2Block,
- apkSigningBlockOffset,
- zipSections.getZipCentralDirectoryOffset(),
- zipSections.getZipEndOfCentralDirectoryOffset(),
- eocd);
- }
-
- /**
- * Returns the APK Signing Block and its offset in the provided APK.
- *
- * @throws SignatureNotFoundException if the APK does not contain an APK Signing Block
- */
- public static Pair<DataSource, Long> findApkSigningBlock(
- DataSource apk, ApkUtils.ZipSections zipSections)
- throws IOException, SignatureNotFoundException {
- // FORMAT:
- // OFFSET DATA TYPE DESCRIPTION
- // * @+0 bytes uint64: size in bytes (excluding this field)
- // * @+8 bytes payload
- // * @-24 bytes uint64: size in bytes (same as the one above)
- // * @-16 bytes uint128: magic
-
- long centralDirStartOffset = zipSections.getZipCentralDirectoryOffset();
- long centralDirEndOffset =
- centralDirStartOffset + zipSections.getZipCentralDirectorySizeBytes();
- long eocdStartOffset = zipSections.getZipEndOfCentralDirectoryOffset();
- if (centralDirEndOffset != eocdStartOffset) {
- throw new SignatureNotFoundException(
- "ZIP Central Directory is not immediately followed by End of Central Directory"
- + ". CD end: " + centralDirEndOffset
- + ", EoCD start: " + eocdStartOffset);
- }
-
- if (centralDirStartOffset < APK_SIG_BLOCK_MIN_SIZE) {
- throw new SignatureNotFoundException(
- "APK too small for APK Signing Block. ZIP Central Directory offset: "
- + centralDirStartOffset);
- }
- // Read the magic and offset in file from the footer section of the block:
- // * uint64: size of block
- // * 16 bytes: magic
- ByteBuffer footer = apk.getByteBuffer(centralDirStartOffset - 24, 24);
- footer.order(ByteOrder.LITTLE_ENDIAN);
- if ((footer.getLong(8) != APK_SIG_BLOCK_MAGIC_LO)
- || (footer.getLong(16) != APK_SIG_BLOCK_MAGIC_HI)) {
- throw new SignatureNotFoundException(
- "No APK Signing Block before ZIP Central Directory");
- }
- // Read and compare size fields
- long apkSigBlockSizeInFooter = footer.getLong(0);
- if ((apkSigBlockSizeInFooter < footer.capacity())
- || (apkSigBlockSizeInFooter > Integer.MAX_VALUE - 8)) {
- throw new SignatureNotFoundException(
- "APK Signing Block size out of range: " + apkSigBlockSizeInFooter);
- }
- int totalSize = (int) (apkSigBlockSizeInFooter + 8);
- long apkSigBlockOffset = centralDirStartOffset - totalSize;
- if (apkSigBlockOffset < 0) {
- throw new SignatureNotFoundException(
- "APK Signing Block offset out of range: " + apkSigBlockOffset);
- }
- ByteBuffer apkSigBlock = apk.getByteBuffer(apkSigBlockOffset, 8);
- apkSigBlock.order(ByteOrder.LITTLE_ENDIAN);
- long apkSigBlockSizeInHeader = apkSigBlock.getLong(0);
- if (apkSigBlockSizeInHeader != apkSigBlockSizeInFooter) {
- throw new SignatureNotFoundException(
- "APK Signing Block sizes in header and footer do not match: "
- + apkSigBlockSizeInHeader + " vs " + apkSigBlockSizeInFooter);
- }
- return Pair.of(apk.slice(apkSigBlockOffset, totalSize), apkSigBlockOffset);
- }
-
- private static ByteBuffer findApkSignatureSchemeV2Block(
- ByteBuffer apkSigningBlock,
- Result result) throws SignatureNotFoundException {
- checkByteOrderLittleEndian(apkSigningBlock);
- // FORMAT:
- // OFFSET DATA TYPE DESCRIPTION
- // * @+0 bytes uint64: size in bytes (excluding this field)
- // * @+8 bytes pairs
- // * @-24 bytes uint64: size in bytes (same as the one above)
- // * @-16 bytes uint128: magic
- ByteBuffer pairs = sliceFromTo(apkSigningBlock, 8, apkSigningBlock.capacity() - 24);
-
- int entryCount = 0;
- while (pairs.hasRemaining()) {
- entryCount++;
- if (pairs.remaining() < 8) {
- throw new SignatureNotFoundException(
- "Insufficient data to read size of APK Signing Block entry #" + entryCount);
- }
- long lenLong = pairs.getLong();
- if ((lenLong < 4) || (lenLong > Integer.MAX_VALUE)) {
- throw new SignatureNotFoundException(
- "APK Signing Block entry #" + entryCount
- + " size out of range: " + lenLong);
- }
- int len = (int) lenLong;
- int nextEntryPos = pairs.position() + len;
- if (len > pairs.remaining()) {
- throw new SignatureNotFoundException(
- "APK Signing Block entry #" + entryCount + " size out of range: " + len
- + ", available: " + pairs.remaining());
- }
- int id = pairs.getInt();
- if (id == APK_SIGNATURE_SCHEME_V2_BLOCK_ID) {
- return getByteBuffer(pairs, len - 4);
- }
- result.addWarning(Issue.APK_SIG_BLOCK_UNKNOWN_ENTRY_ID, id);
- pairs.position(nextEntryPos);
- }
-
- throw new SignatureNotFoundException(
- "No APK Signature Scheme v2 block in APK Signing Block");
- }
-
- private static void checkByteOrderLittleEndian(ByteBuffer buffer) {
- if (buffer.order() != ByteOrder.LITTLE_ENDIAN) {
- throw new IllegalArgumentException("ByteBuffer byte order must be little endian");
- }
- }
-
- public static class SignatureNotFoundException extends Exception {
- private static final long serialVersionUID = 1L;
-
- public SignatureNotFoundException(String message) {
- super(message);
- }
-
- public SignatureNotFoundException(String message, Throwable cause) {
- super(message, cause);
- }
- }
-
- /**
- * Returns new byte buffer whose content is a shared subsequence of this buffer's content
- * between the specified start (inclusive) and end (exclusive) positions. As opposed to
- * {@link ByteBuffer#slice()}, the returned buffer's byte order is the same as the source
- * buffer's byte order.
- */
- private static ByteBuffer sliceFromTo(ByteBuffer source, int start, int end) {
- if (start < 0) {
- throw new IllegalArgumentException("start: " + start);
- }
- if (end < start) {
- throw new IllegalArgumentException("end < start: " + end + " < " + start);
- }
- int capacity = source.capacity();
- if (end > source.capacity()) {
- throw new IllegalArgumentException("end > capacity: " + end + " > " + capacity);
- }
- int originalLimit = source.limit();
- int originalPosition = source.position();
- try {
- source.position(0);
- source.limit(end);
- source.position(start);
- ByteBuffer result = source.slice();
- result.order(source.order());
- return result;
- } finally {
- source.position(0);
- source.limit(originalLimit);
- source.position(originalPosition);
- }
- }
-
- /**
- * Relative <em>get</em> method for reading {@code size} number of bytes from the current
- * position of this buffer.
- *
- * <p>This method reads the next {@code size} bytes at this buffer's current position,
- * returning them as a {@code ByteBuffer} with start set to 0, limit and capacity set to
- * {@code size}, byte order set to this buffer's byte order; and then increments the position by
- * {@code size}.
- */
- private static ByteBuffer getByteBuffer(ByteBuffer source, int size)
- throws BufferUnderflowException {
- if (size < 0) {
- throw new IllegalArgumentException("size: " + size);
- }
- int originalLimit = source.limit();
- int position = source.position();
- int limit = position + size;
- if ((limit < position) || (limit > originalLimit)) {
- throw new BufferUnderflowException();
- }
- source.limit(limit);
- try {
- ByteBuffer result = source.slice();
- result.order(source.order());
- source.position(limit);
- return result;
- } finally {
- source.limit(originalLimit);
- }
- }
-
- private static ByteBuffer getLengthPrefixedSlice(ByteBuffer source) throws IOException {
- if (source.remaining() < 4) {
- throw new IOException(
- "Remaining buffer too short to contain length of length-prefixed field."
- + " Remaining: " + source.remaining());
- }
- int len = source.getInt();
- if (len < 0) {
- throw new IllegalArgumentException("Negative length");
- } else if (len > source.remaining()) {
- throw new IOException("Length-prefixed field longer than remaining buffer."
- + " Field length: " + len + ", remaining: " + source.remaining());
- }
- return getByteBuffer(source, len);
- }
-
- private static byte[] readLengthPrefixedByteArray(ByteBuffer buf) throws IOException {
- int len = buf.getInt();
- if (len < 0) {
- throw new IOException("Negative length");
- } else if (len > buf.remaining()) {
- throw new IOException("Underflow while reading length-prefixed value. Length: " + len
- + ", available: " + buf.remaining());
- }
- byte[] result = new byte[len];
- buf.get(result);
- return result;
- }
-
- /**
- * {@link X509Certificate} whose {@link #getEncoded()} returns the data provided at construction
- * time.
- */
- private static class GuaranteedEncodedFormX509Certificate extends DelegatingX509Certificate {
- private byte[] mEncodedForm;
-
- public GuaranteedEncodedFormX509Certificate(X509Certificate wrapped, byte[] encodedForm) {
- super(wrapped);
- this.mEncodedForm = (encodedForm != null) ? encodedForm.clone() : null;
- }
-
- @Override
- public byte[] getEncoded() throws CertificateEncodingException {
- return (mEncodedForm != null) ? mEncodedForm.clone() : null;
- }
- }
-
- private static final char[] HEX_DIGITS = "01234567890abcdef".toCharArray();
-
- private static String toHex(byte[] value) {
- StringBuilder sb = new StringBuilder(value.length * 2);
- int len = value.length;
- for (int i = 0; i < len; i++) {
- int hi = (value[i] & 0xff) >>> 4;
- int lo = value[i] & 0x0f;
- sb.append(HEX_DIGITS[hi]).append(HEX_DIGITS[lo]);
- }
- return sb.toString();
- }
-
- public static class Result {
-
- /** Whether the APK's APK Signature Scheme v2 signature verifies. */
- public boolean verified;
-
- public final List<SignerInfo> signers = new ArrayList<>();
- private final List<IssueWithParams> mWarnings = new ArrayList<>();
- private final List<IssueWithParams> mErrors = new ArrayList<>();
-
- public boolean containsErrors() {
- if (!mErrors.isEmpty()) {
- return true;
- }
- if (!signers.isEmpty()) {
- for (SignerInfo signer : signers) {
- if (signer.containsErrors()) {
- return true;
- }
- }
- }
- return false;
- }
-
- public void addError(Issue msg, Object... parameters) {
- mErrors.add(new IssueWithParams(msg, parameters));
- }
-
- public void addWarning(Issue msg, Object... parameters) {
- mWarnings.add(new IssueWithParams(msg, parameters));
- }
-
- public List<IssueWithParams> getErrors() {
- return mErrors;
- }
-
- public List<IssueWithParams> getWarnings() {
- return mWarnings;
- }
-
- public static class SignerInfo {
- public int index;
- public List<X509Certificate> certs = new ArrayList<>();
- public List<ContentDigest> contentDigests = new ArrayList<>();
- public Map<ContentDigestAlgorithm, byte[]> verifiedContentDigests = new HashMap<>();
- public List<Signature> signatures = new ArrayList<>();
- public Map<SignatureAlgorithm, byte[]> verifiedSignatures = new HashMap<>();
- public List<AdditionalAttribute> additionalAttributes = new ArrayList<>();
- public byte[] signedData;
-
- private final List<IssueWithParams> mWarnings = new ArrayList<>();
- private final List<IssueWithParams> mErrors = new ArrayList<>();
-
- public void addError(Issue msg, Object... parameters) {
- mErrors.add(new IssueWithParams(msg, parameters));
- }
-
- public void addWarning(Issue msg, Object... parameters) {
- mWarnings.add(new IssueWithParams(msg, parameters));
- }
-
- public boolean containsErrors() {
- return !mErrors.isEmpty();
- }
-
- public List<IssueWithParams> getErrors() {
- return mErrors;
- }
-
- public List<IssueWithParams> getWarnings() {
- return mWarnings;
- }
-
- public static class ContentDigest {
- private final int mSignatureAlgorithmId;
- private final byte[] mValue;
-
- public ContentDigest(int signatureAlgorithmId, byte[] value) {
- mSignatureAlgorithmId = signatureAlgorithmId;
- mValue = value;
- }
-
- public int getSignatureAlgorithmId() {
- return mSignatureAlgorithmId;
- }
-
- public byte[] getValue() {
- return mValue;
- }
- }
-
- public static class Signature {
- private final int mAlgorithmId;
- private final byte[] mValue;
-
- public Signature(int algorithmId, byte[] value) {
- mAlgorithmId = algorithmId;
- mValue = value;
- }
-
- public int getAlgorithmId() {
- return mAlgorithmId;
- }
-
- public byte[] getValue() {
- return mValue;
- }
- }
-
- public static class AdditionalAttribute {
- private final int mId;
- private final byte[] mValue;
-
- public AdditionalAttribute(int id, byte[] value) {
- mId = id;
- mValue = value.clone();
- }
-
- public int getId() {
- return mId;
- }
-
- public byte[] getValue() {
- return mValue.clone();
- }
- }
- }
- }
-}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/jar/ManifestParser.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/jar/ManifestParser.java
deleted file mode 100644
index 793300c90f..0000000000
--- a/tools/apksigner/core/src/com/android/apksigner/core/internal/jar/ManifestParser.java
+++ /dev/null
@@ -1,335 +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.apksigner.core.internal.jar;
-
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.jar.Attributes;
-
-/**
- * JAR manifest and signature file parser.
- *
- * <p>These files consist of a main section followed by individual sections. Individual sections
- * are named, their names referring to JAR entries.
- *
- * @see <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#JAR_Manifest">JAR Manifest format</a>
- */
-public class ManifestParser {
-
- private final byte[] mManifest;
- private int mOffset;
- private int mEndOffset;
-
- private String mBufferedLine;
-
- /**
- * Constructs a new {@code ManifestParser} with the provided input.
- */
- public ManifestParser(byte[] data) {
- this(data, 0, data.length);
- }
-
- /**
- * Constructs a new {@code ManifestParser} with the provided input.
- */
- public ManifestParser(byte[] data, int offset, int length) {
- mManifest = data;
- mOffset = offset;
- mEndOffset = offset + length;
- }
-
- /**
- * Returns the remaining sections of this file.
- */
- public List<Section> readAllSections() {
- List<Section> sections = new ArrayList<>();
- Section section;
- while ((section = readSection()) != null) {
- sections.add(section);
- }
- return sections;
- }
-
- /**
- * Returns the next section from this file or {@code null} if end of file has been reached.
- */
- public Section readSection() {
- // Locate the first non-empty line
- int sectionStartOffset;
- String attr;
- do {
- sectionStartOffset = mOffset;
- attr = readAttribute();
- if (attr == null) {
- return null;
- }
- } while (attr.length() == 0);
- List<Attribute> attrs = new ArrayList<>();
- attrs.add(parseAttr(attr));
-
- // Read attributes until end of section reached
- while (true) {
- attr = readAttribute();
- if ((attr == null) || (attr.length() == 0)) {
- // End of section
- break;
- }
- attrs.add(parseAttr(attr));
- }
-
- int sectionEndOffset = mOffset;
- int sectionSizeBytes = sectionEndOffset - sectionStartOffset;
-
- return new Section(sectionStartOffset, sectionSizeBytes, attrs);
- }
-
- private static Attribute parseAttr(String attr) {
- int delimiterIndex = attr.indexOf(':');
- if (delimiterIndex == -1) {
- return new Attribute(attr.trim(), "");
- } else {
- return new Attribute(
- attr.substring(0, delimiterIndex).trim(),
- attr.substring(delimiterIndex + 1).trim());
- }
- }
-
- /**
- * Returns the next attribute or empty {@code String} if end of section has been reached or
- * {@code null} if end of input has been reached.
- */
- private String readAttribute() {
- // Check whether end of section was reached during previous invocation
- if ((mBufferedLine != null) && (mBufferedLine.length() == 0)) {
- mBufferedLine = null;
- return "";
- }
-
- // Read the next line
- String line = readLine();
- if (line == null) {
- // End of input
- if (mBufferedLine != null) {
- String result = mBufferedLine;
- mBufferedLine = null;
- return result;
- }
- return null;
- }
-
- // Consume the read line
- if (line.length() == 0) {
- // End of section
- if (mBufferedLine != null) {
- String result = mBufferedLine;
- mBufferedLine = "";
- return result;
- }
- return "";
- }
- StringBuilder attrLine;
- if (mBufferedLine == null) {
- attrLine = new StringBuilder(line);
- } else {
- if (!line.startsWith(" ")) {
- // The most common case: buffered line is a full attribute
- String result = mBufferedLine;
- mBufferedLine = line;
- return result;
- }
- attrLine = new StringBuilder(mBufferedLine);
- mBufferedLine = null;
- attrLine.append(line.substring(1));
- }
-
- // Everything's buffered in attrLine now. mBufferedLine is null
-
- // Read more lines
- while (true) {
- line = readLine();
- if (line == null) {
- // End of input
- return attrLine.toString();
- } else if (line.length() == 0) {
- // End of section
- mBufferedLine = ""; // make this method return "end of section" next time
- return attrLine.toString();
- }
- if (line.startsWith(" ")) {
- // Continuation line
- attrLine.append(line.substring(1));
- } else {
- // Next attribute
- mBufferedLine = line;
- return attrLine.toString();
- }
- }
- }
-
- /**
- * Returns the next line (without line delimiter characters) or {@code null} if end of input has
- * been reached.
- */
- private String readLine() {
- if (mOffset >= mEndOffset) {
- return null;
- }
- int startOffset = mOffset;
- int newlineStartOffset = -1;
- int newlineEndOffset = -1;
- for (int i = startOffset; i < mEndOffset; i++) {
- byte b = mManifest[i];
- if (b == '\r') {
- newlineStartOffset = i;
- int nextIndex = i + 1;
- if ((nextIndex < mEndOffset) && (mManifest[nextIndex] == '\n')) {
- newlineEndOffset = nextIndex + 1;
- break;
- }
- newlineEndOffset = nextIndex;
- break;
- } else if (b == '\n') {
- newlineStartOffset = i;
- newlineEndOffset = i + 1;
- break;
- }
- }
- if (newlineStartOffset == -1) {
- newlineStartOffset = mEndOffset;
- newlineEndOffset = mEndOffset;
- }
- mOffset = newlineEndOffset;
-
- int lineLengthBytes = newlineStartOffset - startOffset;
- if (lineLengthBytes == 0) {
- return "";
- }
- return new String(mManifest, startOffset, lineLengthBytes, StandardCharsets.UTF_8);
- }
-
-
- /**
- * Attribute.
- */
- public static class Attribute {
- private final String mName;
- private final String mValue;
-
- /**
- * Constructs a new {@code Attribute} with the provided name and value.
- */
- public Attribute(String name, String value) {
- mName = name;
- mValue = value;
- }
-
- /**
- * Returns this attribute's name.
- */
- public String getName() {
- return mName;
- }
-
- /**
- * Returns this attribute's value.
- */
- public String getValue() {
- return mValue;
- }
- }
-
- /**
- * Section.
- */
- public static class Section {
- private final int mStartOffset;
- private final int mSizeBytes;
- private final String mName;
- private final List<Attribute> mAttributes;
-
- /**
- * Constructs a new {@code Section}.
- *
- * @param startOffset start offset (in bytes) of the section in the input file
- * @param sizeBytes size (in bytes) of the section in the input file
- * @param attrs attributes contained in the section
- */
- public Section(int startOffset, int sizeBytes, List<Attribute> attrs) {
- mStartOffset = startOffset;
- mSizeBytes = sizeBytes;
- String sectionName = null;
- if (!attrs.isEmpty()) {
- Attribute firstAttr = attrs.get(0);
- if ("Name".equalsIgnoreCase(firstAttr.getName())) {
- sectionName = firstAttr.getValue();
- }
- }
- mName = sectionName;
- mAttributes = Collections.unmodifiableList(new ArrayList<>(attrs));
- }
-
- public String getName() {
- return mName;
- }
-
- /**
- * Returns the offset (in bytes) at which this section starts in the input.
- */
- public int getStartOffset() {
- return mStartOffset;
- }
-
- /**
- * Returns the size (in bytes) of this section in the input.
- */
- public int getSizeBytes() {
- return mSizeBytes;
- }
-
- /**
- * Returns this section's attributes, in the order in which they appear in the input.
- */
- public List<Attribute> getAttributes() {
- return mAttributes;
- }
-
- /**
- * Returns the value of the specified attribute in this section or {@code null} if this
- * section does not contain a matching attribute.
- */
- public String getAttributeValue(Attributes.Name name) {
- return getAttributeValue(name.toString());
- }
-
- /**
- * Returns the value of the specified attribute in this section or {@code null} if this
- * section does not contain a matching attribute.
- *
- * @param name name of the attribute. Attribute names are case-insensitive.
- */
- public String getAttributeValue(String name) {
- for (Attribute attr : mAttributes) {
- if (attr.getName().equalsIgnoreCase(name)) {
- return attr.getValue();
- }
- }
- return null;
- }
- }
-}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/jar/ManifestWriter.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/jar/ManifestWriter.java
deleted file mode 100644
index 13b1aaf9ed..0000000000
--- a/tools/apksigner/core/src/com/android/apksigner/core/internal/jar/ManifestWriter.java
+++ /dev/null
@@ -1,127 +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.apksigner.core.internal.jar;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.charset.StandardCharsets;
-import java.util.Map;
-import java.util.Set;
-import java.util.SortedMap;
-import java.util.TreeMap;
-import java.util.jar.Attributes;
-
-/**
- * Producer of {@code META-INF/MANIFEST.MF} file.
- *
- * @see <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#JAR_Manifest">JAR Manifest format</a>
- */
-public abstract class ManifestWriter {
-
- private static final byte[] CRLF = new byte[] {'\r', '\n'};
- private static final int MAX_LINE_LENGTH = 70;
-
- private ManifestWriter() {}
-
- public static void writeMainSection(OutputStream out, Attributes attributes)
- throws IOException {
-
- // Main section must start with the Manifest-Version attribute.
- // See https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signed_JAR_File.
- String manifestVersion = attributes.getValue(Attributes.Name.MANIFEST_VERSION);
- if (manifestVersion == null) {
- throw new IllegalArgumentException(
- "Mandatory " + Attributes.Name.MANIFEST_VERSION + " attribute missing");
- }
- writeAttribute(out, Attributes.Name.MANIFEST_VERSION, manifestVersion);
-
- if (attributes.size() > 1) {
- SortedMap<String, String> namedAttributes = getAttributesSortedByName(attributes);
- namedAttributes.remove(Attributes.Name.MANIFEST_VERSION.toString());
- writeAttributes(out, namedAttributes);
- }
- writeSectionDelimiter(out);
- }
-
- public static void writeIndividualSection(OutputStream out, String name, Attributes attributes)
- throws IOException {
- writeAttribute(out, "Name", name);
-
- if (!attributes.isEmpty()) {
- writeAttributes(out, getAttributesSortedByName(attributes));
- }
- writeSectionDelimiter(out);
- }
-
- static void writeSectionDelimiter(OutputStream out) throws IOException {
- out.write(CRLF);
- }
-
- static void writeAttribute(OutputStream out, Attributes.Name name, String value)
- throws IOException {
- writeAttribute(out, name.toString(), value);
- }
-
- private static void writeAttribute(OutputStream out, String name, String value)
- throws IOException {
- writeLine(out, name + ": " + value);
- }
-
- private static void writeLine(OutputStream out, String line) throws IOException {
- byte[] lineBytes = line.getBytes(StandardCharsets.UTF_8);
- int offset = 0;
- int remaining = lineBytes.length;
- boolean firstLine = true;
- while (remaining > 0) {
- int chunkLength;
- if (firstLine) {
- // First line
- chunkLength = Math.min(remaining, MAX_LINE_LENGTH);
- } else {
- // Continuation line
- out.write(CRLF);
- out.write(' ');
- chunkLength = Math.min(remaining, MAX_LINE_LENGTH - 1);
- }
- out.write(lineBytes, offset, chunkLength);
- offset += chunkLength;
- remaining -= chunkLength;
- firstLine = false;
- }
- out.write(CRLF);
- }
-
- static SortedMap<String, String> getAttributesSortedByName(Attributes attributes) {
- Set<Map.Entry<Object, Object>> attributesEntries = attributes.entrySet();
- SortedMap<String, String> namedAttributes = new TreeMap<String, String>();
- for (Map.Entry<Object, Object> attribute : attributesEntries) {
- String attrName = attribute.getKey().toString();
- String attrValue = attribute.getValue().toString();
- namedAttributes.put(attrName, attrValue);
- }
- return namedAttributes;
- }
-
- static void writeAttributes(
- OutputStream out, SortedMap<String, String> attributesSortedByName) throws IOException {
- for (Map.Entry<String, String> attribute : attributesSortedByName.entrySet()) {
- String attrName = attribute.getKey();
- String attrValue = attribute.getValue();
- writeAttribute(out, attrName, attrValue);
- }
- }
-}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/jar/SignatureFileWriter.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/jar/SignatureFileWriter.java
deleted file mode 100644
index 94ae280ca2..0000000000
--- a/tools/apksigner/core/src/com/android/apksigner/core/internal/jar/SignatureFileWriter.java
+++ /dev/null
@@ -1,61 +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.apksigner.core.internal.jar;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.SortedMap;
-import java.util.jar.Attributes;
-
-/**
- * Producer of JAR signature file ({@code *.SF}).
- *
- * @see <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#JAR_Manifest">JAR Manifest format</a>
- */
-public abstract class SignatureFileWriter {
- private SignatureFileWriter() {}
-
- public static void writeMainSection(OutputStream out, Attributes attributes)
- throws IOException {
-
- // Main section must start with the Signature-Version attribute.
- // See https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signed_JAR_File.
- String signatureVersion = attributes.getValue(Attributes.Name.SIGNATURE_VERSION);
- if (signatureVersion == null) {
- throw new IllegalArgumentException(
- "Mandatory " + Attributes.Name.SIGNATURE_VERSION + " attribute missing");
- }
- ManifestWriter.writeAttribute(out, Attributes.Name.SIGNATURE_VERSION, signatureVersion);
-
- if (attributes.size() > 1) {
- SortedMap<String, String> namedAttributes =
- ManifestWriter.getAttributesSortedByName(attributes);
- namedAttributes.remove(Attributes.Name.SIGNATURE_VERSION.toString());
- ManifestWriter.writeAttributes(out, namedAttributes);
- }
- writeSectionDelimiter(out);
- }
-
- public static void writeIndividualSection(OutputStream out, String name, Attributes attributes)
- throws IOException {
- ManifestWriter.writeIndividualSection(out, name, attributes);
- }
-
- public static void writeSectionDelimiter(OutputStream out) throws IOException {
- ManifestWriter.writeSectionDelimiter(out);
- }
-}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/util/AndroidSdkVersion.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/util/AndroidSdkVersion.java
deleted file mode 100644
index 3e9d0390fc..0000000000
--- a/tools/apksigner/core/src/com/android/apksigner/core/internal/util/AndroidSdkVersion.java
+++ /dev/null
@@ -1,39 +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.apksigner.core.internal.util;
-
-/**
- * Android SDK version / API Level constants.
- */
-public abstract class AndroidSdkVersion {
-
- /** Hidden constructor to prevent instantiation. */
- private AndroidSdkVersion() {}
-
- /** Android 2.3. */
- public static final int GINGERBREAD = 9;
-
- /** Android 4.3. The revenge of the beans. */
- public static final int JELLY_BEAN_MR2 = 18;
-
- /** Android 5.0. A flat one with beautiful shadows. But still tasty. */
- public static final int LOLLIPOP = 21;
-
- // TODO: Update Javadoc / constant name once N is assigned a proper name / version code.
- /** Android N. */
- public static final int N = 24;
-}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/util/ByteBufferDataSource.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/util/ByteBufferDataSource.java
deleted file mode 100644
index b2d9ca115d..0000000000
--- a/tools/apksigner/core/src/com/android/apksigner/core/internal/util/ByteBufferDataSource.java
+++ /dev/null
@@ -1,126 +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.apksigner.core.internal.util;
-
-import com.android.apksigner.core.util.DataSink;
-import com.android.apksigner.core.util.DataSource;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-
-/**
- * {@link DataSource} backed by a {@link ByteBuffer}.
- */
-public class ByteBufferDataSource implements DataSource {
-
- private final ByteBuffer mBuffer;
- private final int mSize;
-
- /**
- * Constructs a new {@code ByteBufferDigestSource} based on the data contained in the provided
- * buffer between the buffer's position and limit.
- */
- public ByteBufferDataSource(ByteBuffer buffer) {
- this(buffer, true);
- }
-
- /**
- * Constructs a new {@code ByteBufferDigestSource} based on the data contained in the provided
- * buffer between the buffer's position and limit.
- */
- private ByteBufferDataSource(ByteBuffer buffer, boolean sliceRequired) {
- mBuffer = (sliceRequired) ? buffer.slice() : buffer;
- mSize = buffer.remaining();
- }
-
- @Override
- public long size() {
- return mSize;
- }
-
- @Override
- public ByteBuffer getByteBuffer(long offset, int size) {
- checkChunkValid(offset, size);
-
- // checkChunkValid ensures that it's OK to cast offset to int.
- int chunkPosition = (int) offset;
- int chunkLimit = chunkPosition + size;
- // Creating a slice of ByteBuffer modifies the state of the source ByteBuffer (position
- // and limit fields, to be more specific). We thus use synchronization around these
- // state-changing operations to make instances of this class thread-safe.
- synchronized (mBuffer) {
- // ByteBuffer.limit(int) and .position(int) check that that the position >= limit
- // invariant is not broken. Thus, the only way to safely change position and limit
- // without caring about their current values is to first set position to 0 or set the
- // limit to capacity.
- mBuffer.position(0);
-
- mBuffer.limit(chunkLimit);
- mBuffer.position(chunkPosition);
- return mBuffer.slice();
- }
- }
-
- @Override
- public void copyTo(long offset, int size, ByteBuffer dest) {
- dest.put(getByteBuffer(offset, size));
- }
-
- @Override
- public void feed(long offset, long size, DataSink sink) throws IOException {
- if ((size < 0) || (size > mSize)) {
- throw new IllegalArgumentException("size: " + size + ", source size: " + mSize);
- }
- sink.consume(getByteBuffer(offset, (int) size));
- }
-
- @Override
- public ByteBufferDataSource slice(long offset, long size) {
- if ((offset == 0) && (size == mSize)) {
- return this;
- }
- if ((size < 0) || (size > mSize)) {
- throw new IllegalArgumentException("size: " + size + ", source size: " + mSize);
- }
- return new ByteBufferDataSource(
- getByteBuffer(offset, (int) size),
- false // no need to slice -- it's already a slice
- );
- }
-
- private void checkChunkValid(long offset, long size) {
- if (offset < 0) {
- throw new IllegalArgumentException("offset: " + offset);
- }
- if (size < 0) {
- throw new IllegalArgumentException("size: " + size);
- }
- if (offset > mSize) {
- throw new IllegalArgumentException(
- "offset (" + offset + ") > source size (" + mSize + ")");
- }
- long endOffset = offset + size;
- if (endOffset < offset) {
- throw new IllegalArgumentException(
- "offset (" + offset + ") + size (" + size + ") overflow");
- }
- if (endOffset > mSize) {
- throw new IllegalArgumentException(
- "offset (" + offset + ") + size (" + size + ") > source size (" + mSize +")");
- }
- }
-}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/util/ByteBufferSink.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/util/ByteBufferSink.java
deleted file mode 100644
index 8c57905373..0000000000
--- a/tools/apksigner/core/src/com/android/apksigner/core/internal/util/ByteBufferSink.java
+++ /dev/null
@@ -1,56 +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.apksigner.core.internal.util;
-
-import com.android.apksigner.core.util.DataSink;
-
-import java.io.IOException;
-import java.nio.BufferOverflowException;
-import java.nio.ByteBuffer;
-
-/**
- * Data sink which stores all received data into the associated {@link ByteBuffer}.
- */
-public class ByteBufferSink implements DataSink {
-
- private final ByteBuffer mBuffer;
-
- public ByteBufferSink(ByteBuffer buffer) {
- mBuffer = buffer;
- }
-
- @Override
- public void consume(byte[] buf, int offset, int length) throws IOException {
- try {
- mBuffer.put(buf, offset, length);
- } catch (BufferOverflowException e) {
- throw new IOException(
- "Insufficient space in output buffer for " + length + " bytes", e);
- }
- }
-
- @Override
- public void consume(ByteBuffer buf) throws IOException {
- int length = buf.remaining();
- try {
- mBuffer.put(buf);
- } catch (BufferOverflowException e) {
- throw new IOException(
- "Insufficient space in output buffer for " + length + " bytes", e);
- }
- }
-}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/util/DelegatingX509Certificate.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/util/DelegatingX509Certificate.java
deleted file mode 100644
index 936cfa9105..0000000000
--- a/tools/apksigner/core/src/com/android/apksigner/core/internal/util/DelegatingX509Certificate.java
+++ /dev/null
@@ -1,179 +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.apksigner.core.internal.util;
-
-import java.math.BigInteger;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.Principal;
-import java.security.PublicKey;
-import java.security.SignatureException;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateExpiredException;
-import java.security.cert.CertificateNotYetValidException;
-import java.security.cert.X509Certificate;
-import java.util.Date;
-import java.util.Set;
-
-/**
- * {@link X509Certificate} which delegates all method invocations to the provided delegate
- * {@code X509Certificate}.
- */
-public class DelegatingX509Certificate extends X509Certificate {
- private final X509Certificate mDelegate;
-
- public DelegatingX509Certificate(X509Certificate delegate) {
- this.mDelegate = delegate;
- }
-
- @Override
- public Set<String> getCriticalExtensionOIDs() {
- return mDelegate.getCriticalExtensionOIDs();
- }
-
- @Override
- public byte[] getExtensionValue(String oid) {
- return mDelegate.getExtensionValue(oid);
- }
-
- @Override
- public Set<String> getNonCriticalExtensionOIDs() {
- return mDelegate.getNonCriticalExtensionOIDs();
- }
-
- @Override
- public boolean hasUnsupportedCriticalExtension() {
- return mDelegate.hasUnsupportedCriticalExtension();
- }
-
- @Override
- public void checkValidity()
- throws CertificateExpiredException, CertificateNotYetValidException {
- mDelegate.checkValidity();
- }
-
- @Override
- public void checkValidity(Date date)
- throws CertificateExpiredException, CertificateNotYetValidException {
- mDelegate.checkValidity(date);
- }
-
- @Override
- public int getVersion() {
- return mDelegate.getVersion();
- }
-
- @Override
- public BigInteger getSerialNumber() {
- return mDelegate.getSerialNumber();
- }
-
- @Override
- public Principal getIssuerDN() {
- return mDelegate.getIssuerDN();
- }
-
- @Override
- public Principal getSubjectDN() {
- return mDelegate.getSubjectDN();
- }
-
- @Override
- public Date getNotBefore() {
- return mDelegate.getNotBefore();
- }
-
- @Override
- public Date getNotAfter() {
- return mDelegate.getNotAfter();
- }
-
- @Override
- public byte[] getTBSCertificate() throws CertificateEncodingException {
- return mDelegate.getTBSCertificate();
- }
-
- @Override
- public byte[] getSignature() {
- return mDelegate.getSignature();
- }
-
- @Override
- public String getSigAlgName() {
- return mDelegate.getSigAlgName();
- }
-
- @Override
- public String getSigAlgOID() {
- return mDelegate.getSigAlgOID();
- }
-
- @Override
- public byte[] getSigAlgParams() {
- return mDelegate.getSigAlgParams();
- }
-
- @Override
- public boolean[] getIssuerUniqueID() {
- return mDelegate.getIssuerUniqueID();
- }
-
- @Override
- public boolean[] getSubjectUniqueID() {
- return mDelegate.getSubjectUniqueID();
- }
-
- @Override
- public boolean[] getKeyUsage() {
- return mDelegate.getKeyUsage();
- }
-
- @Override
- public int getBasicConstraints() {
- return mDelegate.getBasicConstraints();
- }
-
- @Override
- public byte[] getEncoded() throws CertificateEncodingException {
- return mDelegate.getEncoded();
- }
-
- @Override
- public void verify(PublicKey key) throws CertificateException, NoSuchAlgorithmException,
- InvalidKeyException, NoSuchProviderException, SignatureException {
- mDelegate.verify(key);
- }
-
- @Override
- public void verify(PublicKey key, String sigProvider)
- throws CertificateException, NoSuchAlgorithmException, InvalidKeyException,
- NoSuchProviderException, SignatureException {
- mDelegate.verify(key, sigProvider);
- }
-
- @Override
- public String toString() {
- return mDelegate.toString();
- }
-
- @Override
- public PublicKey getPublicKey() {
- return mDelegate.getPublicKey();
- }
-}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/util/InclusiveIntRange.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/util/InclusiveIntRange.java
deleted file mode 100644
index baf3655945..0000000000
--- a/tools/apksigner/core/src/com/android/apksigner/core/internal/util/InclusiveIntRange.java
+++ /dev/null
@@ -1,89 +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.apksigner.core.internal.util;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Inclusive interval of integers.
- */
-public class InclusiveIntRange {
- private final int min;
- private final int max;
-
- private InclusiveIntRange(int min, int max) {
- this.min = min;
- this.max = max;
- }
-
- public int getMin() {
- return min;
- }
-
- public int getMax() {
- return max;
- }
-
- public static InclusiveIntRange fromTo(int min, int max) {
- return new InclusiveIntRange(min, max);
- }
-
- public static InclusiveIntRange from(int min) {
- return new InclusiveIntRange(min, Integer.MAX_VALUE);
- }
-
- public List<InclusiveIntRange> getValuesNotIn(
- List<InclusiveIntRange> sortedNonOverlappingRanges) {
- if (sortedNonOverlappingRanges.isEmpty()) {
- return Collections.singletonList(this);
- }
-
- int testValue = min;
- List<InclusiveIntRange> result = null;
- for (InclusiveIntRange range : sortedNonOverlappingRanges) {
- int rangeMax = range.max;
- if (testValue > rangeMax) {
- continue;
- }
- int rangeMin = range.min;
- if (testValue < range.min) {
- if (result == null) {
- result = new ArrayList<>();
- }
- result.add(fromTo(testValue, rangeMin - 1));
- }
- if (rangeMax >= max) {
- return (result != null) ? result : Collections.emptyList();
- }
- testValue = rangeMax + 1;
- }
- if (testValue <= max) {
- if (result == null) {
- result = new ArrayList<>(1);
- }
- result.add(fromTo(testValue, max));
- }
- return (result != null) ? result : Collections.emptyList();
- }
-
- @Override
- public String toString() {
- return "[" + min + ", " + ((max < Integer.MAX_VALUE) ? (max + "]") : "\u221e)");
- }
-}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/util/MessageDigestSink.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/util/MessageDigestSink.java
deleted file mode 100644
index 45bb30e02d..0000000000
--- a/tools/apksigner/core/src/com/android/apksigner/core/internal/util/MessageDigestSink.java
+++ /dev/null
@@ -1,52 +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.apksigner.core.internal.util;
-
-import com.android.apksigner.core.util.DataSink;
-
-import java.nio.ByteBuffer;
-import java.security.MessageDigest;
-
-/**
- * Data sink which feeds all received data into the associated {@link MessageDigest} instances. Each
- * {@code MessageDigest} instance receives the same data.
- */
-public class MessageDigestSink implements DataSink {
-
- private final MessageDigest[] mMessageDigests;
-
- public MessageDigestSink(MessageDigest[] digests) {
- mMessageDigests = digests;
- }
-
- @Override
- public void consume(byte[] buf, int offset, int length) {
- for (MessageDigest md : mMessageDigests) {
- md.update(buf, offset, length);
- }
- }
-
- @Override
- public void consume(ByteBuffer buf) {
- int originalPosition = buf.position();
- for (MessageDigest md : mMessageDigests) {
- // Reset the position back to the original because the previous iteration's
- // MessageDigest.update set the buffer's position to the buffer's limit.
- buf.position(originalPosition);
- md.update(buf);
- }
- }
-}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/util/OutputStreamDataSink.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/util/OutputStreamDataSink.java
deleted file mode 100644
index bf18ca0cb5..0000000000
--- a/tools/apksigner/core/src/com/android/apksigner/core/internal/util/OutputStreamDataSink.java
+++ /dev/null
@@ -1,78 +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.apksigner.core.internal.util;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.ByteBuffer;
-
-import com.android.apksigner.core.util.DataSink;
-
-/**
- * {@link DataSink} which outputs received data into the associated {@link OutputStream}.
- */
-public class OutputStreamDataSink implements DataSink {
-
- private static final int MAX_READ_CHUNK_SIZE = 65536;
-
- private final OutputStream mOut;
-
- /**
- * Constructs a new {@code OutputStreamDataSink} which outputs received data into the provided
- * {@link OutputStream}.
- */
- public OutputStreamDataSink(OutputStream out) {
- if (out == null) {
- throw new NullPointerException("out == null");
- }
- mOut = out;
- }
-
- /**
- * Returns {@link OutputStream} into which this data sink outputs received data.
- */
- public OutputStream getOutputStream() {
- return mOut;
- }
-
- @Override
- public void consume(byte[] buf, int offset, int length) throws IOException {
- mOut.write(buf, offset, length);
- }
-
- @Override
- public void consume(ByteBuffer buf) throws IOException {
- if (!buf.hasRemaining()) {
- return;
- }
-
- if (buf.hasArray()) {
- mOut.write(
- buf.array(),
- buf.arrayOffset() + buf.position(),
- buf.remaining());
- buf.position(buf.limit());
- } else {
- byte[] tmp = new byte[Math.min(buf.remaining(), MAX_READ_CHUNK_SIZE)];
- while (buf.hasRemaining()) {
- int chunkSize = Math.min(buf.remaining(), tmp.length);
- buf.get(tmp, 0, chunkSize);
- mOut.write(tmp, 0, chunkSize);
- }
- }
- }
-}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/util/Pair.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/util/Pair.java
deleted file mode 100644
index d59af410b3..0000000000
--- a/tools/apksigner/core/src/com/android/apksigner/core/internal/util/Pair.java
+++ /dev/null
@@ -1,81 +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.apksigner.core.internal.util;
-
-/**
- * Pair of two elements.
- */
-public final class Pair<A, B> {
- private final A mFirst;
- private final B mSecond;
-
- private Pair(A first, B second) {
- mFirst = first;
- mSecond = second;
- }
-
- public static <A, B> Pair<A, B> of(A first, B second) {
- return new Pair<A, B>(first, second);
- }
-
- public A getFirst() {
- return mFirst;
- }
-
- public B getSecond() {
- return mSecond;
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + ((mFirst == null) ? 0 : mFirst.hashCode());
- result = prime * result + ((mSecond == null) ? 0 : mSecond.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null) {
- return false;
- }
- if (getClass() != obj.getClass()) {
- return false;
- }
- @SuppressWarnings("rawtypes")
- Pair other = (Pair) obj;
- if (mFirst == null) {
- if (other.mFirst != null) {
- return false;
- }
- } else if (!mFirst.equals(other.mFirst)) {
- return false;
- }
- if (mSecond == null) {
- if (other.mSecond != null) {
- return false;
- }
- } else if (!mSecond.equals(other.mSecond)) {
- return false;
- }
- return true;
- }
-}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/util/RandomAccessFileDataSink.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/util/RandomAccessFileDataSink.java
deleted file mode 100644
index 2198492289..0000000000
--- a/tools/apksigner/core/src/com/android/apksigner/core/internal/util/RandomAccessFileDataSink.java
+++ /dev/null
@@ -1,87 +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.apksigner.core.internal.util;
-
-import com.android.apksigner.core.util.DataSink;
-
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
-
-/**
- * {@link DataSink} which outputs received data into the associated file, sequentially.
- */
-public class RandomAccessFileDataSink implements DataSink {
-
- private final RandomAccessFile mFile;
- private final FileChannel mFileChannel;
- private long mPosition;
-
- /**
- * Constructs a new {@code RandomAccessFileDataSink} which stores output starting from the
- * beginning of the provided file.
- */
- public RandomAccessFileDataSink(RandomAccessFile file) {
- this(file, 0);
- }
-
- /**
- * Constructs a new {@code RandomAccessFileDataSink} which stores output starting from the
- * specified position of the provided file.
- */
- public RandomAccessFileDataSink(RandomAccessFile file, long startPosition) {
- if (file == null) {
- throw new NullPointerException("file == null");
- }
- if (startPosition < 0) {
- throw new IllegalArgumentException("startPosition: " + startPosition);
- }
- mFile = file;
- mFileChannel = file.getChannel();
- mPosition = startPosition;
- }
-
- @Override
- public void consume(byte[] buf, int offset, int length) throws IOException {
- if (length == 0) {
- return;
- }
-
- synchronized (mFile) {
- mFile.seek(mPosition);
- mFile.write(buf, offset, length);
- mPosition += length;
- }
- }
-
- @Override
- public void consume(ByteBuffer buf) throws IOException {
- int length = buf.remaining();
- if (length == 0) {
- return;
- }
-
- synchronized (mFile) {
- mFile.seek(mPosition);
- while (buf.hasRemaining()) {
- mFileChannel.write(buf);
- }
- mPosition += length;
- }
- }
-}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/util/RandomAccessFileDataSource.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/util/RandomAccessFileDataSource.java
deleted file mode 100644
index 208033d6c8..0000000000
--- a/tools/apksigner/core/src/com/android/apksigner/core/internal/util/RandomAccessFileDataSource.java
+++ /dev/null
@@ -1,165 +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.apksigner.core.internal.util;
-
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
-
-import com.android.apksigner.core.util.DataSink;
-import com.android.apksigner.core.util.DataSource;
-
-/**
- * {@link DataSource} backed by a {@link RandomAccessFile}.
- */
-public class RandomAccessFileDataSource implements DataSource {
-
- private static final int MAX_READ_CHUNK_SIZE = 65536;
-
- private final RandomAccessFile mFile;
- private final long mOffset;
- private final long mSize;
-
- /**
- * Constructs a new {@code RandomAccessFileDataSource} based on the data contained in the
- * specified the whole file. Changes to the contents of the file, including the size of the
- * file, will be visible in this data source.
- */
- public RandomAccessFileDataSource(RandomAccessFile file) {
- mFile = file;
- mOffset = 0;
- mSize = -1;
- }
-
- /**
- * Constructs a new {@code RandomAccessFileDataSource} based on the data contained in the
- * specified region of the provided file. Changes to the contents of the file will be visible in
- * this data source.
- */
- public RandomAccessFileDataSource(RandomAccessFile file, long offset, long size) {
- if (offset < 0) {
- throw new IllegalArgumentException("offset: " + size);
- }
- if (size < 0) {
- throw new IllegalArgumentException("size: " + size);
- }
- mFile = file;
- mOffset = offset;
- mSize = size;
- }
-
- @Override
- public long size() {
- if (mSize == -1) {
- try {
- return mFile.length();
- } catch (IOException e) {
- return 0;
- }
- } else {
- return mSize;
- }
- }
-
- @Override
- public RandomAccessFileDataSource slice(long offset, long size) {
- long sourceSize = size();
- checkChunkValid(offset, size, sourceSize);
- if ((offset == 0) && (size == sourceSize)) {
- return this;
- }
-
- return new RandomAccessFileDataSource(mFile, mOffset + offset, size);
- }
-
- @Override
- public void feed(long offset, long size, DataSink sink) throws IOException {
- long sourceSize = size();
- checkChunkValid(offset, size, sourceSize);
- if (size == 0) {
- return;
- }
-
- long chunkOffsetInFile = mOffset + offset;
- long remaining = size;
- byte[] buf = new byte[(int) Math.min(remaining, MAX_READ_CHUNK_SIZE)];
- while (remaining > 0) {
- int chunkSize = (int) Math.min(remaining, buf.length);
- synchronized (mFile) {
- mFile.seek(chunkOffsetInFile);
- mFile.readFully(buf, 0, chunkSize);
- }
- sink.consume(buf, 0, chunkSize);
- chunkOffsetInFile += chunkSize;
- remaining -= chunkSize;
- }
- }
-
- @Override
- public void copyTo(long offset, int size, ByteBuffer dest) throws IOException {
- long sourceSize = size();
- checkChunkValid(offset, size, sourceSize);
- if (size == 0) {
- return;
- }
-
- long offsetInFile = mOffset + offset;
- int remaining = size;
- FileChannel fileChannel = mFile.getChannel();
- while (remaining > 0) {
- int chunkSize;
- synchronized (mFile) {
- fileChannel.position(offsetInFile);
- chunkSize = fileChannel.read(dest);
- }
- offsetInFile += chunkSize;
- remaining -= chunkSize;
- }
- }
-
- @Override
- public ByteBuffer getByteBuffer(long offset, int size) throws IOException {
- ByteBuffer result = ByteBuffer.allocate(size);
- copyTo(offset, size, result);
- result.flip();
- return result;
- }
-
- private static void checkChunkValid(long offset, long size, long sourceSize) {
- if (offset < 0) {
- throw new IllegalArgumentException("offset: " + offset);
- }
- if (size < 0) {
- throw new IllegalArgumentException("size: " + size);
- }
- if (offset > sourceSize) {
- throw new IllegalArgumentException(
- "offset (" + offset + ") > source size (" + sourceSize + ")");
- }
- long endOffset = offset + size;
- if (endOffset < offset) {
- throw new IllegalArgumentException(
- "offset (" + offset + ") + size (" + size + ") overflow");
- }
- if (endOffset > sourceSize) {
- throw new IllegalArgumentException(
- "offset (" + offset + ") + size (" + size
- + ") > source size (" + sourceSize +")");
- }
- }
-}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/zip/CentralDirectoryRecord.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/zip/CentralDirectoryRecord.java
deleted file mode 100644
index 141d01e1c2..0000000000
--- a/tools/apksigner/core/src/com/android/apksigner/core/internal/zip/CentralDirectoryRecord.java
+++ /dev/null
@@ -1,281 +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.apksigner.core.internal.zip;
-
-import com.android.apksigner.core.zip.ZipFormatException;
-
-import java.nio.BufferUnderflowException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.charset.StandardCharsets;
-import java.util.Comparator;
-
-/**
- * ZIP Central Directory (CD) Record.
- */
-public class CentralDirectoryRecord {
-
- /**
- * Comparator which compares records by the offset of the corresponding Local File Header in the
- * archive.
- */
- public static final Comparator<CentralDirectoryRecord> BY_LOCAL_FILE_HEADER_OFFSET_COMPARATOR =
- new ByLocalFileHeaderOffsetComparator();
-
- private static final int RECORD_SIGNATURE = 0x02014b50;
- private static final int HEADER_SIZE_BYTES = 46;
-
- private static final int LAST_MODIFICATION_TIME_OFFSET = 12;
- private static final int LOCAL_FILE_HEADER_OFFSET_OFFSET = 42;
- private static final int NAME_OFFSET = HEADER_SIZE_BYTES;
-
- private final ByteBuffer mData;
- private final int mLastModificationTime;
- private final int mLastModificationDate;
- private final long mCrc32;
- private final long mCompressedSize;
- private final long mUncompressedSize;
- private final long mLocalFileHeaderOffset;
- private final String mName;
- private final int mNameSizeBytes;
-
- private CentralDirectoryRecord(
- ByteBuffer data,
- int lastModificationTime,
- int lastModificationDate,
- long crc32,
- long compressedSize,
- long uncompressedSize,
- long localFileHeaderOffset,
- String name,
- int nameSizeBytes) {
- mData = data;
- mLastModificationDate = lastModificationDate;
- mLastModificationTime = lastModificationTime;
- mCrc32 = crc32;
- mCompressedSize = compressedSize;
- mUncompressedSize = uncompressedSize;
- mLocalFileHeaderOffset = localFileHeaderOffset;
- mName = name;
- mNameSizeBytes = nameSizeBytes;
- }
-
- public int getSize() {
- return mData.remaining();
- }
-
- public String getName() {
- return mName;
- }
-
- public int getNameSizeBytes() {
- return mNameSizeBytes;
- }
-
- public int getLastModificationTime() {
- return mLastModificationTime;
- }
-
- public int getLastModificationDate() {
- return mLastModificationDate;
- }
-
- public long getCrc32() {
- return mCrc32;
- }
-
- public long getCompressedSize() {
- return mCompressedSize;
- }
-
- public long getUncompressedSize() {
- return mUncompressedSize;
- }
-
- public long getLocalFileHeaderOffset() {
- return mLocalFileHeaderOffset;
- }
-
- /**
- * Returns the Central Directory Record starting at the current position of the provided buffer
- * and advances the buffer's position immediately past the end of the record.
- */
- public static CentralDirectoryRecord getRecord(ByteBuffer buf) throws ZipFormatException {
- ZipUtils.assertByteOrderLittleEndian(buf);
- if (buf.remaining() < HEADER_SIZE_BYTES) {
- throw new ZipFormatException(
- "Input too short. Need at least: " + HEADER_SIZE_BYTES
- + " bytes, available: " + buf.remaining() + " bytes",
- new BufferUnderflowException());
- }
- int originalPosition = buf.position();
- int recordSignature = buf.getInt();
- if (recordSignature != RECORD_SIGNATURE) {
- throw new ZipFormatException(
- "Not a Central Directory record. Signature: 0x"
- + Long.toHexString(recordSignature & 0xffffffffL));
- }
- buf.position(originalPosition + LAST_MODIFICATION_TIME_OFFSET);
- int lastModificationTime = ZipUtils.getUnsignedInt16(buf);
- int lastModificationDate = ZipUtils.getUnsignedInt16(buf);
- long crc32 = ZipUtils.getUnsignedInt32(buf);
- long compressedSize = ZipUtils.getUnsignedInt32(buf);
- long uncompressedSize = ZipUtils.getUnsignedInt32(buf);
- int nameSize = ZipUtils.getUnsignedInt16(buf);
- int extraSize = ZipUtils.getUnsignedInt16(buf);
- int commentSize = ZipUtils.getUnsignedInt16(buf);
- buf.position(originalPosition + LOCAL_FILE_HEADER_OFFSET_OFFSET);
- long localFileHeaderOffset = ZipUtils.getUnsignedInt32(buf);
- buf.position(originalPosition);
- int recordSize = HEADER_SIZE_BYTES + nameSize + extraSize + commentSize;
- if (recordSize > buf.remaining()) {
- throw new ZipFormatException(
- "Input too short. Need: " + recordSize + " bytes, available: "
- + buf.remaining() + " bytes",
- new BufferUnderflowException());
- }
- String name = getName(buf, originalPosition + NAME_OFFSET, nameSize);
- buf.position(originalPosition);
- int originalLimit = buf.limit();
- int recordEndInBuf = originalPosition + recordSize;
- ByteBuffer recordBuf;
- try {
- buf.limit(recordEndInBuf);
- recordBuf = buf.slice();
- } finally {
- buf.limit(originalLimit);
- }
- // Consume this record
- buf.position(recordEndInBuf);
- return new CentralDirectoryRecord(
- recordBuf,
- lastModificationTime,
- lastModificationDate,
- crc32,
- compressedSize,
- uncompressedSize,
- localFileHeaderOffset,
- name,
- nameSize);
- }
-
- public void copyTo(ByteBuffer output) {
- output.put(mData.slice());
- }
-
- public CentralDirectoryRecord createWithModifiedLocalFileHeaderOffset(
- long localFileHeaderOffset) {
- ByteBuffer result = ByteBuffer.allocate(mData.remaining());
- result.put(mData.slice());
- result.flip();
- result.order(ByteOrder.LITTLE_ENDIAN);
- ZipUtils.setUnsignedInt32(result, LOCAL_FILE_HEADER_OFFSET_OFFSET, localFileHeaderOffset);
- return new CentralDirectoryRecord(
- result,
- mLastModificationTime,
- mLastModificationDate,
- mCrc32,
- mCompressedSize,
- mUncompressedSize,
- localFileHeaderOffset,
- mName,
- mNameSizeBytes);
- }
-
- public static CentralDirectoryRecord createWithDeflateCompressedData(
- String name,
- int lastModifiedTime,
- int lastModifiedDate,
- long crc32,
- long compressedSize,
- long uncompressedSize,
- long localFileHeaderOffset) {
- byte[] nameBytes = name.getBytes(StandardCharsets.UTF_8);
- int recordSize = HEADER_SIZE_BYTES + nameBytes.length;
- ByteBuffer result = ByteBuffer.allocate(recordSize);
- result.order(ByteOrder.LITTLE_ENDIAN);
- result.putInt(RECORD_SIGNATURE);
- ZipUtils.putUnsignedInt16(result, 0x14); // Version made by
- ZipUtils.putUnsignedInt16(result, 0x14); // Minimum version needed to extract
- result.putShort(ZipUtils.GP_FLAG_EFS); // UTF-8 character encoding used for entry name
- result.putShort(ZipUtils.COMPRESSION_METHOD_DEFLATED);
- ZipUtils.putUnsignedInt16(result, lastModifiedTime);
- ZipUtils.putUnsignedInt16(result, lastModifiedDate);
- ZipUtils.putUnsignedInt32(result, crc32);
- ZipUtils.putUnsignedInt32(result, compressedSize);
- ZipUtils.putUnsignedInt32(result, uncompressedSize);
- ZipUtils.putUnsignedInt16(result, nameBytes.length);
- ZipUtils.putUnsignedInt16(result, 0); // Extra field length
- ZipUtils.putUnsignedInt16(result, 0); // File comment length
- ZipUtils.putUnsignedInt16(result, 0); // Disk number
- ZipUtils.putUnsignedInt16(result, 0); // Internal file attributes
- ZipUtils.putUnsignedInt32(result, 0); // External file attributes
- ZipUtils.putUnsignedInt32(result, localFileHeaderOffset);
- result.put(nameBytes);
-
- if (result.hasRemaining()) {
- throw new RuntimeException("pos: " + result.position() + ", limit: " + result.limit());
- }
- result.flip();
- return new CentralDirectoryRecord(
- result,
- lastModifiedTime,
- lastModifiedDate,
- crc32,
- compressedSize,
- uncompressedSize,
- localFileHeaderOffset,
- name,
- nameBytes.length);
- }
-
- static String getName(ByteBuffer record, int position, int nameLengthBytes) {
- byte[] nameBytes;
- int nameBytesOffset;
- if (record.hasArray()) {
- nameBytes = record.array();
- nameBytesOffset = record.arrayOffset() + position;
- } else {
- nameBytes = new byte[nameLengthBytes];
- nameBytesOffset = 0;
- int originalPosition = record.position();
- try {
- record.position(position);
- record.get(nameBytes);
- } finally {
- record.position(originalPosition);
- }
- }
- return new String(nameBytes, nameBytesOffset, nameLengthBytes, StandardCharsets.UTF_8);
- }
-
- private static class ByLocalFileHeaderOffsetComparator
- implements Comparator<CentralDirectoryRecord> {
- @Override
- public int compare(CentralDirectoryRecord r1, CentralDirectoryRecord r2) {
- long offset1 = r1.getLocalFileHeaderOffset();
- long offset2 = r2.getLocalFileHeaderOffset();
- if (offset1 > offset2) {
- return 1;
- } else if (offset1 < offset2) {
- return -1;
- } else {
- return 0;
- }
- }
- }
-}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/zip/EocdRecord.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/zip/EocdRecord.java
deleted file mode 100644
index 8777591300..0000000000
--- a/tools/apksigner/core/src/com/android/apksigner/core/internal/zip/EocdRecord.java
+++ /dev/null
@@ -1,48 +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.apksigner.core.internal.zip;
-
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-
-/**
- * ZIP End of Central Directory record.
- */
-public class EocdRecord {
- private static final int CD_RECORD_COUNT_ON_DISK_OFFSET = 8;
- private static final int CD_RECORD_COUNT_TOTAL_OFFSET = 10;
- private static final int CD_SIZE_OFFSET = 12;
- private static final int CD_OFFSET_OFFSET = 16;
-
- public static ByteBuffer createWithModifiedCentralDirectoryInfo(
- ByteBuffer original,
- int centralDirectoryRecordCount,
- long centralDirectorySizeBytes,
- long centralDirectoryOffset) {
- ByteBuffer result = ByteBuffer.allocate(original.remaining());
- result.order(ByteOrder.LITTLE_ENDIAN);
- result.put(original.slice());
- result.flip();
- ZipUtils.setUnsignedInt16(
- result, CD_RECORD_COUNT_ON_DISK_OFFSET, centralDirectoryRecordCount);
- ZipUtils.setUnsignedInt16(
- result, CD_RECORD_COUNT_TOTAL_OFFSET, centralDirectoryRecordCount);
- ZipUtils.setUnsignedInt32(result, CD_SIZE_OFFSET, centralDirectorySizeBytes);
- ZipUtils.setUnsignedInt32(result, CD_OFFSET_OFFSET, centralDirectoryOffset);
- return result;
- }
-}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/zip/LocalFileRecord.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/zip/LocalFileRecord.java
deleted file mode 100644
index 397a450080..0000000000
--- a/tools/apksigner/core/src/com/android/apksigner/core/internal/zip/LocalFileRecord.java
+++ /dev/null
@@ -1,540 +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.apksigner.core.internal.zip;
-
-import java.io.Closeable;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.charset.StandardCharsets;
-import java.util.zip.DataFormatException;
-import java.util.zip.Inflater;
-
-import com.android.apksigner.core.internal.util.ByteBufferSink;
-import com.android.apksigner.core.util.DataSink;
-import com.android.apksigner.core.util.DataSource;
-import com.android.apksigner.core.zip.ZipFormatException;
-
-/**
- * ZIP Local File record.
- *
- * <p>The record consists of the Local File Header, file data, and (if present) Data Descriptor.
- */
-public class LocalFileRecord {
- private static final int RECORD_SIGNATURE = 0x04034b50;
- private static final int HEADER_SIZE_BYTES = 30;
-
- private static final int GP_FLAGS_OFFSET = 6;
- private static final int COMPRESSION_METHOD_OFFSET = 8;
- private static final int CRC32_OFFSET = 14;
- private static final int COMPRESSED_SIZE_OFFSET = 18;
- private static final int UNCOMPRESSED_SIZE_OFFSET = 22;
- private static final int NAME_LENGTH_OFFSET = 26;
- private static final int EXTRA_LENGTH_OFFSET = 28;
- private static final int NAME_OFFSET = HEADER_SIZE_BYTES;
-
- private static final int DATA_DESCRIPTOR_SIZE_BYTES_WITHOUT_SIGNATURE = 12;
- private static final int DATA_DESCRIPTOR_SIGNATURE = 0x08074b50;
-
- private final String mName;
- private final int mNameSizeBytes;
- private final ByteBuffer mExtra;
-
- private final long mStartOffsetInArchive;
- private final long mSize;
-
- private final int mDataStartOffset;
- private final long mDataSize;
- private final boolean mDataCompressed;
- private final long mUncompressedDataSize;
-
- private LocalFileRecord(
- String name,
- int nameSizeBytes,
- ByteBuffer extra,
- long startOffsetInArchive,
- long size,
- int dataStartOffset,
- long dataSize,
- boolean dataCompressed,
- long uncompressedDataSize) {
- mName = name;
- mNameSizeBytes = nameSizeBytes;
- mExtra = extra;
- mStartOffsetInArchive = startOffsetInArchive;
- mSize = size;
- mDataStartOffset = dataStartOffset;
- mDataSize = dataSize;
- mDataCompressed = dataCompressed;
- mUncompressedDataSize = uncompressedDataSize;
- }
-
- public String getName() {
- return mName;
- }
-
- public ByteBuffer getExtra() {
- return (mExtra.capacity() > 0) ? mExtra.slice() : mExtra;
- }
-
- public int getExtraFieldStartOffsetInsideRecord() {
- return HEADER_SIZE_BYTES + mNameSizeBytes;
- }
-
- public long getStartOffsetInArchive() {
- return mStartOffsetInArchive;
- }
-
- public int getDataStartOffsetInRecord() {
- return mDataStartOffset;
- }
-
- /**
- * Returns the size (in bytes) of this record.
- */
- public long getSize() {
- return mSize;
- }
-
- /**
- * Returns {@code true} if this record's file data is stored in compressed form.
- */
- public boolean isDataCompressed() {
- return mDataCompressed;
- }
-
- /**
- * Returns the Local File record starting at the current position of the provided buffer
- * and advances the buffer's position immediately past the end of the record. The record
- * consists of the Local File Header, data, and (if present) Data Descriptor.
- */
- public static LocalFileRecord getRecord(
- DataSource apk,
- CentralDirectoryRecord cdRecord,
- long cdStartOffset) throws ZipFormatException, IOException {
- return getRecord(
- apk,
- cdRecord,
- cdStartOffset,
- true, // obtain extra field contents
- true // include Data Descriptor (if present)
- );
- }
-
- /**
- * Returns the Local File record starting at the current position of the provided buffer
- * and advances the buffer's position immediately past the end of the record. The record
- * consists of the Local File Header, data, and (if present) Data Descriptor.
- */
- private static LocalFileRecord getRecord(
- DataSource apk,
- CentralDirectoryRecord cdRecord,
- long cdStartOffset,
- boolean extraFieldContentsNeeded,
- boolean dataDescriptorIncluded) throws ZipFormatException, IOException {
- // IMPLEMENTATION NOTE: This method attempts to mimic the behavior of Android platform
- // exhibited when reading an APK for the purposes of verifying its signatures.
-
- String entryName = cdRecord.getName();
- int cdRecordEntryNameSizeBytes = cdRecord.getNameSizeBytes();
- int headerSizeWithName = HEADER_SIZE_BYTES + cdRecordEntryNameSizeBytes;
- long headerStartOffset = cdRecord.getLocalFileHeaderOffset();
- long headerEndOffset = headerStartOffset + headerSizeWithName;
- if (headerEndOffset >= cdStartOffset) {
- throw new ZipFormatException(
- "Local File Header of " + entryName + " extends beyond start of Central"
- + " Directory. LFH end: " + headerEndOffset
- + ", CD start: " + cdStartOffset);
- }
- ByteBuffer header;
- try {
- header = apk.getByteBuffer(headerStartOffset, headerSizeWithName);
- } catch (IOException e) {
- throw new IOException("Failed to read Local File Header of " + entryName, e);
- }
- header.order(ByteOrder.LITTLE_ENDIAN);
-
- int recordSignature = header.getInt();
- if (recordSignature != RECORD_SIGNATURE) {
- throw new ZipFormatException(
- "Not a Local File Header record for entry " + entryName + ". Signature: 0x"
- + Long.toHexString(recordSignature & 0xffffffffL));
- }
- short gpFlags = header.getShort(GP_FLAGS_OFFSET);
- boolean dataDescriptorUsed = (gpFlags & ZipUtils.GP_FLAG_DATA_DESCRIPTOR_USED) != 0;
- long uncompressedDataCrc32FromCdRecord = cdRecord.getCrc32();
- long compressedDataSizeFromCdRecord = cdRecord.getCompressedSize();
- long uncompressedDataSizeFromCdRecord = cdRecord.getUncompressedSize();
- if (!dataDescriptorUsed) {
- long crc32 = ZipUtils.getUnsignedInt32(header, CRC32_OFFSET);
- if (crc32 != uncompressedDataCrc32FromCdRecord) {
- throw new ZipFormatException(
- "CRC-32 mismatch between Local File Header and Central Directory for entry "
- + entryName + ". LFH: " + crc32
- + ", CD: " + uncompressedDataCrc32FromCdRecord);
- }
- long compressedSize = ZipUtils.getUnsignedInt32(header, COMPRESSED_SIZE_OFFSET);
- if (compressedSize != compressedDataSizeFromCdRecord) {
- throw new ZipFormatException(
- "Compressed size mismatch between Local File Header and Central Directory"
- + " for entry " + entryName + ". LFH: " + compressedSize
- + ", CD: " + compressedDataSizeFromCdRecord);
- }
- long uncompressedSize = ZipUtils.getUnsignedInt32(header, UNCOMPRESSED_SIZE_OFFSET);
- if (uncompressedSize != uncompressedDataSizeFromCdRecord) {
- throw new ZipFormatException(
- "Uncompressed size mismatch between Local File Header and Central Directory"
- + " for entry " + entryName + ". LFH: " + uncompressedSize
- + ", CD: " + uncompressedDataSizeFromCdRecord);
- }
- }
- int nameLength = ZipUtils.getUnsignedInt16(header, NAME_LENGTH_OFFSET);
- if (nameLength > cdRecordEntryNameSizeBytes) {
- throw new ZipFormatException(
- "Name mismatch between Local File Header and Central Directory for entry"
- + entryName + ". LFH: " + nameLength
- + " bytes, CD: " + cdRecordEntryNameSizeBytes + " bytes");
- }
- String name = CentralDirectoryRecord.getName(header, NAME_OFFSET, nameLength);
- if (!entryName.equals(name)) {
- throw new ZipFormatException(
- "Name mismatch between Local File Header and Central Directory. LFH: \""
- + name + "\", CD: \"" + entryName + "\"");
- }
- int extraLength = ZipUtils.getUnsignedInt16(header, EXTRA_LENGTH_OFFSET);
-
- short compressionMethod = header.getShort(COMPRESSION_METHOD_OFFSET);
- boolean compressed;
- switch (compressionMethod) {
- case ZipUtils.COMPRESSION_METHOD_STORED:
- compressed = false;
- break;
- case ZipUtils.COMPRESSION_METHOD_DEFLATED:
- compressed = true;
- break;
- default:
- throw new ZipFormatException(
- "Unsupported compression method of entry " + entryName
- + ": " + (compressionMethod & 0xffff));
- }
-
- long dataStartOffset = headerStartOffset + HEADER_SIZE_BYTES + nameLength + extraLength;
- long dataSize;
- if (compressed) {
- dataSize = compressedDataSizeFromCdRecord;
- } else {
- dataSize = uncompressedDataSizeFromCdRecord;
- }
- long dataEndOffset = dataStartOffset + dataSize;
- if (dataEndOffset > cdStartOffset) {
- throw new ZipFormatException(
- "Local File Header data of " + entryName + " overlaps with Central Directory"
- + ". LFH data start: " + dataStartOffset
- + ", LFH data end: " + dataEndOffset + ", CD start: " + cdStartOffset);
- }
-
- ByteBuffer extra = EMPTY_BYTE_BUFFER;
- if ((extraFieldContentsNeeded) && (extraLength > 0)) {
- extra = apk.getByteBuffer(
- headerStartOffset + HEADER_SIZE_BYTES + nameLength, extraLength);
- }
-
- long recordEndOffset = dataEndOffset;
- // Include the Data Descriptor (if requested and present) into the record.
- if ((dataDescriptorIncluded) && ((gpFlags & ZipUtils.GP_FLAG_DATA_DESCRIPTOR_USED) != 0)) {
- // The record's data is supposed to be followed by the Data Descriptor. Unfortunately,
- // the descriptor's size is not known in advance because the spec lets the signature
- // field (the first four bytes) be omitted. Thus, there's no 100% reliable way to tell
- // how long the Data Descriptor record is. Most parsers (including Android) check
- // whether the first four bytes look like Data Descriptor record signature and, if so,
- // assume that it is indeed the record's signature. However, this is the wrong
- // conclusion if the record's CRC-32 (next field after the signature) has the same value
- // as the signature. In any case, we're doing what Android is doing.
- long dataDescriptorEndOffset =
- dataEndOffset + DATA_DESCRIPTOR_SIZE_BYTES_WITHOUT_SIGNATURE;
- if (dataDescriptorEndOffset > cdStartOffset) {
- throw new ZipFormatException(
- "Data Descriptor of " + entryName + " overlaps with Central Directory"
- + ". Data Descriptor end: " + dataEndOffset
- + ", CD start: " + cdStartOffset);
- }
- ByteBuffer dataDescriptorPotentialSig = apk.getByteBuffer(dataEndOffset, 4);
- dataDescriptorPotentialSig.order(ByteOrder.LITTLE_ENDIAN);
- if (dataDescriptorPotentialSig.getInt() == DATA_DESCRIPTOR_SIGNATURE) {
- dataDescriptorEndOffset += 4;
- if (dataDescriptorEndOffset > cdStartOffset) {
- throw new ZipFormatException(
- "Data Descriptor of " + entryName + " overlaps with Central Directory"
- + ". Data Descriptor end: " + dataEndOffset
- + ", CD start: " + cdStartOffset);
- }
- }
- recordEndOffset = dataDescriptorEndOffset;
- }
-
- long recordSize = recordEndOffset - headerStartOffset;
- int dataStartOffsetInRecord = HEADER_SIZE_BYTES + nameLength + extraLength;
-
- return new LocalFileRecord(
- entryName,
- cdRecordEntryNameSizeBytes,
- extra,
- headerStartOffset,
- recordSize,
- dataStartOffsetInRecord,
- dataSize,
- compressed,
- uncompressedDataSizeFromCdRecord);
- }
-
- /**
- * Outputs this record and returns returns the number of bytes output.
- */
- public long outputRecord(DataSource sourceApk, DataSink output) throws IOException {
- long size = getSize();
- sourceApk.feed(getStartOffsetInArchive(), size, output);
- return size;
- }
-
- /**
- * Outputs this record, replacing its extra field with the provided one, and returns returns the
- * number of bytes output.
- */
- public long outputRecordWithModifiedExtra(
- DataSource sourceApk,
- ByteBuffer extra,
- DataSink output) throws IOException {
- long recordStartOffsetInSource = getStartOffsetInArchive();
- int extraStartOffsetInRecord = getExtraFieldStartOffsetInsideRecord();
- int extraSizeBytes = extra.remaining();
- int headerSize = extraStartOffsetInRecord + extraSizeBytes;
- ByteBuffer header = ByteBuffer.allocate(headerSize);
- header.order(ByteOrder.LITTLE_ENDIAN);
- sourceApk.copyTo(recordStartOffsetInSource, extraStartOffsetInRecord, header);
- header.put(extra.slice());
- header.flip();
- ZipUtils.setUnsignedInt16(header, EXTRA_LENGTH_OFFSET, extraSizeBytes);
-
- long outputByteCount = header.remaining();
- output.consume(header);
- long remainingRecordSize = getSize() - mDataStartOffset;
- sourceApk.feed(recordStartOffsetInSource + mDataStartOffset, remainingRecordSize, output);
- outputByteCount += remainingRecordSize;
- return outputByteCount;
- }
-
- /**
- * Outputs the specified Local File Header record with its data and returns the number of bytes
- * output.
- */
- public static long outputRecordWithDeflateCompressedData(
- String name,
- int lastModifiedTime,
- int lastModifiedDate,
- byte[] compressedData,
- long crc32,
- long uncompressedSize,
- DataSink output) throws IOException {
- byte[] nameBytes = name.getBytes(StandardCharsets.UTF_8);
- int recordSize = HEADER_SIZE_BYTES + nameBytes.length;
- ByteBuffer result = ByteBuffer.allocate(recordSize);
- result.order(ByteOrder.LITTLE_ENDIAN);
- result.putInt(RECORD_SIGNATURE);
- ZipUtils.putUnsignedInt16(result, 0x14); // Minimum version needed to extract
- result.putShort(ZipUtils.GP_FLAG_EFS); // General purpose flag: UTF-8 encoded name
- result.putShort(ZipUtils.COMPRESSION_METHOD_DEFLATED);
- ZipUtils.putUnsignedInt16(result, lastModifiedTime);
- ZipUtils.putUnsignedInt16(result, lastModifiedDate);
- ZipUtils.putUnsignedInt32(result, crc32);
- ZipUtils.putUnsignedInt32(result, compressedData.length);
- ZipUtils.putUnsignedInt32(result, uncompressedSize);
- ZipUtils.putUnsignedInt16(result, nameBytes.length);
- ZipUtils.putUnsignedInt16(result, 0); // Extra field length
- result.put(nameBytes);
- if (result.hasRemaining()) {
- throw new RuntimeException("pos: " + result.position() + ", limit: " + result.limit());
- }
- result.flip();
-
- long outputByteCount = result.remaining();
- output.consume(result);
- outputByteCount += compressedData.length;
- output.consume(compressedData, 0, compressedData.length);
- return outputByteCount;
- }
-
- private static final ByteBuffer EMPTY_BYTE_BUFFER = ByteBuffer.allocate(0);
-
- /**
- * Sends uncompressed data of this record into the the provided data sink.
- */
- public void outputUncompressedData(
- DataSource lfhSection,
- DataSink sink) throws IOException, ZipFormatException {
- long dataStartOffsetInArchive = mStartOffsetInArchive + mDataStartOffset;
- try {
- if (mDataCompressed) {
- try (InflateSinkAdapter inflateAdapter = new InflateSinkAdapter(sink)) {
- lfhSection.feed(dataStartOffsetInArchive, mDataSize, inflateAdapter);
- long actualUncompressedSize = inflateAdapter.getOutputByteCount();
- if (actualUncompressedSize != mUncompressedDataSize) {
- throw new ZipFormatException(
- "Unexpected size of uncompressed data of " + mName
- + ". Expected: " + mUncompressedDataSize + " bytes"
- + ", actual: " + actualUncompressedSize + " bytes");
- }
- }
- } else {
- lfhSection.feed(dataStartOffsetInArchive, mDataSize, sink);
- // No need to check whether output size is as expected because DataSource.feed is
- // guaranteed to output exactly the number of bytes requested.
- }
- } catch (IOException e) {
- throw new IOException(
- "Failed to read data of " + ((mDataCompressed) ? "compressed" : "uncompressed")
- + " entry " + mName,
- e);
- }
- // Interestingly, Android doesn't check that uncompressed data's CRC-32 is as expected. We
- // thus don't check either.
- }
-
- /**
- * Sends uncompressed data pointed to by the provided ZIP Central Directory (CD) record into the
- * provided data sink.
- */
- public static void outputUncompressedData(
- DataSource source,
- CentralDirectoryRecord cdRecord,
- long cdStartOffsetInArchive,
- DataSink sink) throws ZipFormatException, IOException {
- // IMPLEMENTATION NOTE: This method attempts to mimic the behavior of Android platform
- // exhibited when reading an APK for the purposes of verifying its signatures.
- // When verifying an APK, Android doesn't care reading the extra field or the Data
- // Descriptor.
- LocalFileRecord lfhRecord =
- getRecord(
- source,
- cdRecord,
- cdStartOffsetInArchive,
- false, // don't care about the extra field
- false // don't read the Data Descriptor
- );
- lfhRecord.outputUncompressedData(source, sink);
- }
-
- /**
- * Returns the uncompressed data pointed to by the provided ZIP Central Directory (CD) record.
- */
- public static byte[] getUncompressedData(
- DataSource source,
- CentralDirectoryRecord cdRecord,
- long cdStartOffsetInArchive) throws ZipFormatException, IOException {
- if (cdRecord.getUncompressedSize() > Integer.MAX_VALUE) {
- throw new IOException(
- cdRecord.getName() + " too large: " + cdRecord.getUncompressedSize());
- }
- byte[] result = new byte[(int) cdRecord.getUncompressedSize()];
- ByteBuffer resultBuf = ByteBuffer.wrap(result);
- ByteBufferSink resultSink = new ByteBufferSink(resultBuf);
- outputUncompressedData(
- source,
- cdRecord,
- cdStartOffsetInArchive,
- resultSink);
- return result;
- }
-
- /**
- * {@link DataSink} which inflates received data and outputs the deflated data into the provided
- * delegate sink.
- */
- private static class InflateSinkAdapter implements DataSink, Closeable {
- private final DataSink mDelegate;
-
- private Inflater mInflater = new Inflater(true);
- private byte[] mOutputBuffer;
- private byte[] mInputBuffer;
- private long mOutputByteCount;
- private boolean mClosed;
-
- private InflateSinkAdapter(DataSink delegate) {
- mDelegate = delegate;
- }
-
- @Override
- public void consume(byte[] buf, int offset, int length) throws IOException {
- checkNotClosed();
- mInflater.setInput(buf, offset, length);
- if (mOutputBuffer == null) {
- mOutputBuffer = new byte[65536];
- }
- while (!mInflater.finished()) {
- int outputChunkSize;
- try {
- outputChunkSize = mInflater.inflate(mOutputBuffer);
- } catch (DataFormatException e) {
- throw new IOException("Failed to inflate data", e);
- }
- if (outputChunkSize == 0) {
- return;
- }
- mDelegate.consume(mOutputBuffer, 0, outputChunkSize);
- mOutputByteCount += outputChunkSize;
- }
- }
-
- @Override
- public void consume(ByteBuffer buf) throws IOException {
- checkNotClosed();
- if (buf.hasArray()) {
- consume(buf.array(), buf.arrayOffset() + buf.position(), buf.remaining());
- buf.position(buf.limit());
- } else {
- if (mInputBuffer == null) {
- mInputBuffer = new byte[65536];
- }
- while (buf.hasRemaining()) {
- int chunkSize = Math.min(buf.remaining(), mInputBuffer.length);
- buf.get(mInputBuffer, 0, chunkSize);
- consume(mInputBuffer, 0, chunkSize);
- }
- }
- }
-
- public long getOutputByteCount() {
- return mOutputByteCount;
- }
-
- @Override
- public void close() throws IOException {
- mClosed = true;
- mInputBuffer = null;
- mOutputBuffer = null;
- if (mInflater != null) {
- mInflater.end();
- mInflater = null;
- }
- }
-
- private void checkNotClosed() {
- if (mClosed) {
- throw new IllegalStateException("Closed");
- }
- }
- }
-}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/zip/ZipUtils.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/zip/ZipUtils.java
deleted file mode 100644
index 6a0c501e91..0000000000
--- a/tools/apksigner/core/src/com/android/apksigner/core/internal/zip/ZipUtils.java
+++ /dev/null
@@ -1,353 +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.apksigner.core.internal.zip;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.zip.CRC32;
-import java.util.zip.Deflater;
-
-import com.android.apksigner.core.internal.util.Pair;
-import com.android.apksigner.core.util.DataSource;
-
-/**
- * Assorted ZIP format helpers.
- *
- * <p>NOTE: Most helper methods operating on {@code ByteBuffer} instances expect that the byte
- * order of these buffers is little-endian.
- */
-public abstract class ZipUtils {
- private ZipUtils() {}
-
- public static final short COMPRESSION_METHOD_STORED = 0;
- public static final short COMPRESSION_METHOD_DEFLATED = 8;
-
- public static final short GP_FLAG_DATA_DESCRIPTOR_USED = 0x08;
- public static final short GP_FLAG_EFS = 0x0800;
-
- private static final int ZIP_EOCD_REC_MIN_SIZE = 22;
- private static final int ZIP_EOCD_REC_SIG = 0x06054b50;
- private static final int ZIP_EOCD_CENTRAL_DIR_TOTAL_RECORD_COUNT_OFFSET = 10;
- private static final int ZIP_EOCD_CENTRAL_DIR_SIZE_FIELD_OFFSET = 12;
- private static final int ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET = 16;
- private static final int ZIP_EOCD_COMMENT_LENGTH_FIELD_OFFSET = 20;
-
- private static final int ZIP64_EOCD_LOCATOR_SIZE = 20;
- private static final int ZIP64_EOCD_LOCATOR_SIG = 0x07064b50;
-
- private static final int UINT16_MAX_VALUE = 0xffff;
-
- /**
- * Sets the offset of the start of the ZIP Central Directory in the archive.
- *
- * <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian.
- */
- public static void setZipEocdCentralDirectoryOffset(
- ByteBuffer zipEndOfCentralDirectory, long offset) {
- assertByteOrderLittleEndian(zipEndOfCentralDirectory);
- setUnsignedInt32(
- zipEndOfCentralDirectory,
- zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET,
- offset);
- }
-
- /**
- * Returns the offset of the start of the ZIP Central Directory in the archive.
- *
- * <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian.
- */
- public static long getZipEocdCentralDirectoryOffset(ByteBuffer zipEndOfCentralDirectory) {
- assertByteOrderLittleEndian(zipEndOfCentralDirectory);
- return getUnsignedInt32(
- zipEndOfCentralDirectory,
- zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET);
- }
-
- /**
- * Returns the size (in bytes) of the ZIP Central Directory.
- *
- * <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian.
- */
- public static long getZipEocdCentralDirectorySizeBytes(ByteBuffer zipEndOfCentralDirectory) {
- assertByteOrderLittleEndian(zipEndOfCentralDirectory);
- return getUnsignedInt32(
- zipEndOfCentralDirectory,
- zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_SIZE_FIELD_OFFSET);
- }
-
- /**
- * Returns the total number of records in ZIP Central Directory.
- *
- * <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian.
- */
- public static int getZipEocdCentralDirectoryTotalRecordCount(
- ByteBuffer zipEndOfCentralDirectory) {
- assertByteOrderLittleEndian(zipEndOfCentralDirectory);
- return getUnsignedInt16(
- zipEndOfCentralDirectory,
- zipEndOfCentralDirectory.position()
- + ZIP_EOCD_CENTRAL_DIR_TOTAL_RECORD_COUNT_OFFSET);
- }
-
- /**
- * Returns the ZIP End of Central Directory record of the provided ZIP file.
- *
- * @return contents of the ZIP End of Central Directory record and the record's offset in the
- * file or {@code null} if the file does not contain the record.
- *
- * @throws IOException if an I/O error occurs while reading the file.
- */
- public static Pair<ByteBuffer, Long> findZipEndOfCentralDirectoryRecord(DataSource zip)
- throws IOException {
- // ZIP End of Central Directory (EOCD) record is located at the very end of the ZIP archive.
- // The record can be identified by its 4-byte signature/magic which is located at the very
- // beginning of the record. A complication is that the record is variable-length because of
- // the comment field.
- // The algorithm for locating the ZIP EOCD record is as follows. We search backwards from
- // end of the buffer for the EOCD record signature. Whenever we find a signature, we check
- // the candidate record's comment length is such that the remainder of the record takes up
- // exactly the remaining bytes in the buffer. The search is bounded because the maximum
- // size of the comment field is 65535 bytes because the field is an unsigned 16-bit number.
-
- long fileSize = zip.size();
- if (fileSize < ZIP_EOCD_REC_MIN_SIZE) {
- return null;
- }
-
- // Optimization: 99.99% of APKs have a zero-length comment field in the EoCD record and thus
- // the EoCD record offset is known in advance. Try that offset first to avoid unnecessarily
- // reading more data.
- Pair<ByteBuffer, Long> result = findZipEndOfCentralDirectoryRecord(zip, 0);
- if (result != null) {
- return result;
- }
-
- // EoCD does not start where we expected it to. Perhaps it contains a non-empty comment
- // field. Expand the search. The maximum size of the comment field in EoCD is 65535 because
- // the comment length field is an unsigned 16-bit number.
- return findZipEndOfCentralDirectoryRecord(zip, UINT16_MAX_VALUE);
- }
-
- /**
- * Returns the ZIP End of Central Directory record of the provided ZIP file.
- *
- * @param maxCommentSize maximum accepted size (in bytes) of EoCD comment field. The permitted
- * value is from 0 to 65535 inclusive. The smaller the value, the faster this method
- * locates the record, provided its comment field is no longer than this value.
- *
- * @return contents of the ZIP End of Central Directory record and the record's offset in the
- * file or {@code null} if the file does not contain the record.
- *
- * @throws IOException if an I/O error occurs while reading the file.
- */
- private static Pair<ByteBuffer, Long> findZipEndOfCentralDirectoryRecord(
- DataSource zip, int maxCommentSize) throws IOException {
- // ZIP End of Central Directory (EOCD) record is located at the very end of the ZIP archive.
- // The record can be identified by its 4-byte signature/magic which is located at the very
- // beginning of the record. A complication is that the record is variable-length because of
- // the comment field.
- // The algorithm for locating the ZIP EOCD record is as follows. We search backwards from
- // end of the buffer for the EOCD record signature. Whenever we find a signature, we check
- // the candidate record's comment length is such that the remainder of the record takes up
- // exactly the remaining bytes in the buffer. The search is bounded because the maximum
- // size of the comment field is 65535 bytes because the field is an unsigned 16-bit number.
-
- if ((maxCommentSize < 0) || (maxCommentSize > UINT16_MAX_VALUE)) {
- throw new IllegalArgumentException("maxCommentSize: " + maxCommentSize);
- }
-
- long fileSize = zip.size();
- if (fileSize < ZIP_EOCD_REC_MIN_SIZE) {
- // No space for EoCD record in the file.
- return null;
- }
- // Lower maxCommentSize if the file is too small.
- maxCommentSize = (int) Math.min(maxCommentSize, fileSize - ZIP_EOCD_REC_MIN_SIZE);
-
- int maxEocdSize = ZIP_EOCD_REC_MIN_SIZE + maxCommentSize;
- long bufOffsetInFile = fileSize - maxEocdSize;
- ByteBuffer buf = zip.getByteBuffer(bufOffsetInFile, maxEocdSize);
- buf.order(ByteOrder.LITTLE_ENDIAN);
- int eocdOffsetInBuf = findZipEndOfCentralDirectoryRecord(buf);
- if (eocdOffsetInBuf == -1) {
- // No EoCD record found in the buffer
- return null;
- }
- // EoCD found
- buf.position(eocdOffsetInBuf);
- ByteBuffer eocd = buf.slice();
- eocd.order(ByteOrder.LITTLE_ENDIAN);
- return Pair.of(eocd, bufOffsetInFile + eocdOffsetInBuf);
- }
-
- /**
- * Returns the position at which ZIP End of Central Directory record starts in the provided
- * buffer or {@code -1} if the record is not present.
- *
- * <p>NOTE: Byte order of {@code zipContents} must be little-endian.
- */
- private static int findZipEndOfCentralDirectoryRecord(ByteBuffer zipContents) {
- assertByteOrderLittleEndian(zipContents);
-
- // ZIP End of Central Directory (EOCD) record is located at the very end of the ZIP archive.
- // The record can be identified by its 4-byte signature/magic which is located at the very
- // beginning of the record. A complication is that the record is variable-length because of
- // the comment field.
- // The algorithm for locating the ZIP EOCD record is as follows. We search backwards from
- // end of the buffer for the EOCD record signature. Whenever we find a signature, we check
- // the candidate record's comment length is such that the remainder of the record takes up
- // exactly the remaining bytes in the buffer. The search is bounded because the maximum
- // size of the comment field is 65535 bytes because the field is an unsigned 16-bit number.
-
- int archiveSize = zipContents.capacity();
- if (archiveSize < ZIP_EOCD_REC_MIN_SIZE) {
- return -1;
- }
- int maxCommentLength = Math.min(archiveSize - ZIP_EOCD_REC_MIN_SIZE, UINT16_MAX_VALUE);
- int eocdWithEmptyCommentStartPosition = archiveSize - ZIP_EOCD_REC_MIN_SIZE;
- for (int expectedCommentLength = 0; expectedCommentLength < maxCommentLength;
- expectedCommentLength++) {
- int eocdStartPos = eocdWithEmptyCommentStartPosition - expectedCommentLength;
- if (zipContents.getInt(eocdStartPos) == ZIP_EOCD_REC_SIG) {
- int actualCommentLength =
- getUnsignedInt16(
- zipContents, eocdStartPos + ZIP_EOCD_COMMENT_LENGTH_FIELD_OFFSET);
- if (actualCommentLength == expectedCommentLength) {
- return eocdStartPos;
- }
- }
- }
-
- return -1;
- }
-
- /**
- * Returns {@code true} if the provided file contains a ZIP64 End of Central Directory
- * Locator.
- *
- * @param zipEndOfCentralDirectoryPosition offset of the ZIP End of Central Directory record
- * in the file.
- *
- * @throws IOException if an I/O error occurs while reading the data source
- */
- public static final boolean isZip64EndOfCentralDirectoryLocatorPresent(
- DataSource zip, long zipEndOfCentralDirectoryPosition) throws IOException {
-
- // ZIP64 End of Central Directory Locator immediately precedes the ZIP End of Central
- // Directory Record.
- long locatorPosition = zipEndOfCentralDirectoryPosition - ZIP64_EOCD_LOCATOR_SIZE;
- if (locatorPosition < 0) {
- return false;
- }
-
- ByteBuffer sig = zip.getByteBuffer(locatorPosition, 4);
- sig.order(ByteOrder.LITTLE_ENDIAN);
- return sig.getInt(0) == ZIP64_EOCD_LOCATOR_SIG;
- }
-
- static void assertByteOrderLittleEndian(ByteBuffer buffer) {
- if (buffer.order() != ByteOrder.LITTLE_ENDIAN) {
- throw new IllegalArgumentException("ByteBuffer byte order must be little endian");
- }
- }
-
- public static int getUnsignedInt16(ByteBuffer buffer, int offset) {
- return buffer.getShort(offset) & 0xffff;
- }
-
- public static int getUnsignedInt16(ByteBuffer buffer) {
- return buffer.getShort() & 0xffff;
- }
-
- static void setUnsignedInt16(ByteBuffer buffer, int offset, int value) {
- if ((value < 0) || (value > 0xffff)) {
- throw new IllegalArgumentException("uint16 value of out range: " + value);
- }
- buffer.putShort(offset, (short) value);
- }
-
- static void setUnsignedInt32(ByteBuffer buffer, int offset, long value) {
- if ((value < 0) || (value > 0xffffffffL)) {
- throw new IllegalArgumentException("uint32 value of out range: " + value);
- }
- buffer.putInt(offset, (int) value);
- }
-
- public static void putUnsignedInt16(ByteBuffer buffer, int value) {
- if ((value < 0) || (value > 0xffff)) {
- throw new IllegalArgumentException("uint16 value of out range: " + value);
- }
- buffer.putShort((short) value);
- }
-
- static long getUnsignedInt32(ByteBuffer buffer, int offset) {
- return buffer.getInt(offset) & 0xffffffffL;
- }
-
- static long getUnsignedInt32(ByteBuffer buffer) {
- return buffer.getInt() & 0xffffffffL;
- }
-
- static void putUnsignedInt32(ByteBuffer buffer, long value) {
- if ((value < 0) || (value > 0xffffffffL)) {
- throw new IllegalArgumentException("uint32 value of out range: " + value);
- }
- buffer.putInt((int) value);
- }
-
- public static DeflateResult deflate(ByteBuffer input) {
- byte[] inputBuf;
- int inputOffset;
- int inputLength = input.remaining();
- if (input.hasArray()) {
- inputBuf = input.array();
- inputOffset = input.arrayOffset() + input.position();
- input.position(input.limit());
- } else {
- inputBuf = new byte[inputLength];
- inputOffset = 0;
- input.get(inputBuf);
- }
- CRC32 crc32 = new CRC32();
- crc32.update(inputBuf, inputOffset, inputLength);
- long crc32Value = crc32.getValue();
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- Deflater deflater = new Deflater(9, true);
- deflater.setInput(inputBuf, inputOffset, inputLength);
- deflater.finish();
- byte[] buf = new byte[65536];
- while (!deflater.finished()) {
- int chunkSize = deflater.deflate(buf);
- out.write(buf, 0, chunkSize);
- }
- return new DeflateResult(inputLength, crc32Value, out.toByteArray());
- }
-
- public static class DeflateResult {
- public final int inputSizeBytes;
- public final long inputCrc32;
- public final byte[] output;
-
- public DeflateResult(int inputSizeBytes, long inputCrc32, byte[] output) {
- this.inputSizeBytes = inputSizeBytes;
- this.inputCrc32 = inputCrc32;
- this.output = output;
- }
- }
-} \ No newline at end of file
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/util/DataSink.java b/tools/apksigner/core/src/com/android/apksigner/core/util/DataSink.java
deleted file mode 100644
index 35a61fceee..0000000000
--- a/tools/apksigner/core/src/com/android/apksigner/core/util/DataSink.java
+++ /dev/null
@@ -1,43 +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.apksigner.core.util;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-
-/**
- * Consumer of input data which may be provided in one go or in chunks.
- */
-public interface DataSink {
-
- /**
- * Consumes the provided chunk of data.
- *
- * <p>This data sink guarantees to not hold references to the provided buffer after this method
- * terminates.
- */
- void consume(byte[] buf, int offset, int length) throws IOException;
-
- /**
- * Consumes all remaining data in the provided buffer and advances the buffer's position
- * to the buffer's limit.
- *
- * <p>This data sink guarantees to not hold references to the provided buffer after this method
- * terminates.
- */
- void consume(ByteBuffer buf) throws IOException;
-}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/util/DataSinks.java b/tools/apksigner/core/src/com/android/apksigner/core/util/DataSinks.java
deleted file mode 100644
index 4aefedb05b..0000000000
--- a/tools/apksigner/core/src/com/android/apksigner/core/util/DataSinks.java
+++ /dev/null
@@ -1,46 +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.apksigner.core.util;
-
-import java.io.OutputStream;
-import java.io.RandomAccessFile;
-
-import com.android.apksigner.core.internal.util.OutputStreamDataSink;
-import com.android.apksigner.core.internal.util.RandomAccessFileDataSink;
-
-/**
- * Utility methods for working with {@link DataSink} abstraction.
- */
-public abstract class DataSinks {
- private DataSinks() {}
-
- /**
- * Returns a {@link DataSink} which outputs received data into the provided
- * {@link OutputStream}.
- */
- public static DataSink asDataSink(OutputStream out) {
- return new OutputStreamDataSink(out);
- }
-
- /**
- * Returns a {@link DataSink} which outputs received data into the provided file, sequentially,
- * starting at the beginning of the file.
- */
- public static DataSink asDataSink(RandomAccessFile file) {
- return new RandomAccessFileDataSink(file);
- }
-}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/util/DataSource.java b/tools/apksigner/core/src/com/android/apksigner/core/util/DataSource.java
deleted file mode 100644
index e268dd29df..0000000000
--- a/tools/apksigner/core/src/com/android/apksigner/core/util/DataSource.java
+++ /dev/null
@@ -1,98 +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.apksigner.core.util;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-
-/**
- * Abstract representation of a source of data.
- *
- * <p>This abstraction serves three purposes:
- * <ul>
- * <li>Transparent handling of different types of sources, such as {@code byte[]},
- * {@link java.nio.ByteBuffer}, {@link java.io.RandomAccessFile}, memory-mapped file.</li>
- * <li>Support sources larger than 2 GB. If all sources were smaller than 2 GB, {@code ByteBuffer}
- * may have worked as the unifying abstraction.</li>
- * <li>Support sources which do not fit into logical memory as a contiguous region.</li>
- * </ul>
- *
- * <p>There are following ways to obtain a chunk of data from the data source:
- * <ul>
- * <li>Stream the chunk's data into a {@link DataSink} using
- * {@link #feed(long, long, DataSink) feed}. This is best suited for scenarios where there is no
- * need to have the chunk's data accessible at the same time, for example, when computing the
- * digest of the chunk. If you need to keep the chunk's data around after {@code feed}
- * completes, you must create a copy during {@code feed}. However, in that case the following
- * methods of obtaining the chunk's data may be more appropriate.</li>
- * <li>Obtain a {@link ByteBuffer} containing the chunk's data using
- * {@link #getByteBuffer(long, int) getByteBuffer}. Depending on the data source, the chunk's
- * data may or may not be copied by this operation. This is best suited for scenarios where
- * you need to access the chunk's data in arbitrary order, but don't need to modify the data and
- * thus don't require a copy of the data.</li>
- * <li>Copy the chunk's data to a {@link ByteBuffer} using
- * {@link #copyTo(long, int, ByteBuffer) copyTo}. This is best suited for scenarios where
- * you require a copy of the chunk's data, such as to when you need to modify the data.
- * </li>
- * </ul>
- */
-public interface DataSource {
-
- /**
- * Returns the amount of data (in bytes) contained in this data source.
- */
- long size();
-
- /**
- * Feeds the specified chunk from this data source into the provided sink.
- *
- * @param offset index (in bytes) at which the chunk starts inside data source
- * @param size size (in bytes) of the chunk
- */
- void feed(long offset, long size, DataSink sink) throws IOException;
-
- /**
- * Returns a buffer holding the contents of the specified chunk of data from this data source.
- * Changes to the data source are not guaranteed to be reflected in the returned buffer.
- * Similarly, changes in the buffer are not guaranteed to be reflected in the data source.
- *
- * <p>The returned buffer's position is {@code 0}, and the buffer's limit and capacity is
- * {@code size}.
- *
- * @param offset index (in bytes) at which the chunk starts inside data source
- * @param size size (in bytes) of the chunk
- */
- ByteBuffer getByteBuffer(long offset, int size) throws IOException;
-
- /**
- * Copies the specified chunk from this data source into the provided destination buffer,
- * advancing the destination buffer's position by {@code size}.
- *
- * @param offset index (in bytes) at which the chunk starts inside data source
- * @param size size (in bytes) of the chunk
- */
- void copyTo(long offset, int size, ByteBuffer dest) throws IOException;
-
- /**
- * Returns a data source representing the specified region of data of this data source. Changes
- * to data represented by this data source will also be visible in the returned data source.
- *
- * @param offset index (in bytes) at which the region starts inside data source
- * @param size size (in bytes) of the region
- */
- DataSource slice(long offset, long size);
-}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/util/DataSources.java b/tools/apksigner/core/src/com/android/apksigner/core/util/DataSources.java
deleted file mode 100644
index 1cbb0af5fa..0000000000
--- a/tools/apksigner/core/src/com/android/apksigner/core/util/DataSources.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package com.android.apksigner.core.util;
-
-import com.android.apksigner.core.internal.util.ByteBufferDataSource;
-import com.android.apksigner.core.internal.util.RandomAccessFileDataSource;
-
-import java.io.RandomAccessFile;
-import java.nio.ByteBuffer;
-
-/**
- * Utility methods for working with {@link DataSource} abstraction.
- */
-public abstract class DataSources {
- private DataSources() {}
-
- /**
- * Returns a {@link DataSource} backed by the provided {@link ByteBuffer}. The data source
- * represents the data contained between the position and limit of the buffer. Changes to the
- * buffer's contents will be visible in the data source.
- */
- public static DataSource asDataSource(ByteBuffer buffer) {
- if (buffer == null) {
- throw new NullPointerException();
- }
- return new ByteBufferDataSource(buffer);
- }
-
- /**
- * Returns a {@link DataSource} backed by the provided {@link RandomAccessFile}. Changes to the
- * file, including changes to size of file, will be visible in the data source.
- */
- public static DataSource asDataSource(RandomAccessFile file) {
- if (file == null) {
- throw new NullPointerException();
- }
- return new RandomAccessFileDataSource(file);
- }
-
- /**
- * Returns a {@link DataSource} backed by the provided region of the {@link RandomAccessFile}.
- * Changes to the file will be visible in the data source.
- */
- public static DataSource asDataSource(RandomAccessFile file, long offset, long size) {
- if (file == null) {
- throw new NullPointerException();
- }
- return new RandomAccessFileDataSource(file, offset, size);
- }
-}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/zip/ZipFormatException.java b/tools/apksigner/core/src/com/android/apksigner/core/zip/ZipFormatException.java
deleted file mode 100644
index 7da57d9f13..0000000000
--- a/tools/apksigner/core/src/com/android/apksigner/core/zip/ZipFormatException.java
+++ /dev/null
@@ -1,32 +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.apksigner.core.zip;
-
-/**
- * Indicates that a ZIP archive is not well-formed.
- */
-public class ZipFormatException extends Exception {
- private static final long serialVersionUID = 1L;
-
- public ZipFormatException(String message) {
- super(message);
- }
-
- public ZipFormatException(String message, Throwable cause) {
- super(message, cause);
- }
-}
diff --git a/tools/signapk/Android.mk b/tools/signapk/Android.mk
index eff066ccd1..4506e2fc77 100644
--- a/tools/signapk/Android.mk
+++ b/tools/signapk/Android.mk
@@ -22,7 +22,7 @@ LOCAL_MODULE := signapk
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_JAR_MANIFEST := SignApk.mf
LOCAL_STATIC_JAVA_LIBRARIES := \
- apksigner-core \
+ apksig \
bouncycastle-host \
bouncycastle-bcpkix-host \
conscrypt-host
diff --git a/tools/signapk/src/com/android/signapk/SignApk.java b/tools/signapk/src/com/android/signapk/SignApk.java
index f1f340d34a..84fddec2d4 100644
--- a/tools/signapk/src/com/android/signapk/SignApk.java
+++ b/tools/signapk/src/com/android/signapk/SignApk.java
@@ -34,12 +34,12 @@ import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import org.conscrypt.OpenSSLProvider;
-import com.android.apksigner.core.ApkSignerEngine;
-import com.android.apksigner.core.DefaultApkSignerEngine;
-import com.android.apksigner.core.apk.ApkUtils;
-import com.android.apksigner.core.util.DataSink;
-import com.android.apksigner.core.util.DataSources;
-import com.android.apksigner.core.zip.ZipFormatException;
+import com.android.apksig.ApkSignerEngine;
+import com.android.apksig.DefaultApkSignerEngine;
+import com.android.apksig.apk.ApkUtils;
+import com.android.apksig.util.DataSink;
+import com.android.apksig.util.DataSources;
+import com.android.apksig.zip.ZipFormatException;
import java.io.Console;
import java.io.BufferedReader;