diff --git a/app/build.gradle b/app/build.gradle index 88120c990c..e4360ba73c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -342,7 +342,9 @@ dependencies { implementation "io.github.sinaweibosdk:core:${weiboSDK}" - implementation "com.lg:gid:1.3.0" + implementation "com.lg:apksig:${apksig}" + + implementation "com.lg:gid:${gid}" implementation "com.louiscad.splitties:splitties-fun-pack-android-base-with-views-dsl:${splitties}" diff --git a/app/src/main/java/com/android/apksig/ApkSigner.java b/app/src/main/java/com/android/apksig/ApkSigner.java deleted file mode 100644 index ca792c4c6a..0000000000 --- a/app/src/main/java/com/android/apksig/ApkSigner.java +++ /dev/null @@ -1,1560 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig; - -import static com.android.apksig.apk.ApkUtils.SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME; - -import com.android.apksig.apk.ApkFormatException; -import com.android.apksig.apk.ApkSigningBlockNotFoundException; -import com.android.apksig.apk.ApkUtils; -import com.android.apksig.apk.MinSdkVersionException; -import com.android.apksig.internal.util.ByteBufferDataSource; -import com.android.apksig.internal.zip.CentralDirectoryRecord; -import com.android.apksig.internal.zip.EocdRecord; -import com.android.apksig.internal.zip.LocalFileRecord; -import com.android.apksig.internal.zip.ZipUtils; -import com.android.apksig.util.DataSink; -import com.android.apksig.util.DataSinks; -import com.android.apksig.util.DataSource; -import com.android.apksig.util.DataSources; -import com.android.apksig.util.ReadableDataSink; -import com.android.apksig.zip.ZipFormatException; - -import java.io.Closeable; -import java.io.File; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.SignatureException; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * APK signer. - * - *

The signer preserves as much of the input APK as possible. For example, it preserves the order - * of APK entries and preserves their contents, including compressed form and alignment of data. - * - *

Use {@link Builder} to obtain instances of this signer. - * - * @see Application Signing - */ -public class ApkSigner { - - /** - * Extensible data block/field header ID used for storing information about alignment of - * uncompressed entries as well as for aligning the entries's data. See ZIP appnote.txt section - * 4.5 Extensible data fields. - */ - private static final short ALIGNMENT_ZIP_EXTRA_DATA_FIELD_HEADER_ID = (short) 0xd935; - - /** - * Minimum size (in bytes) of the extensible data block/field used for alignment of uncompressed - * entries. - */ - private static final short ALIGNMENT_ZIP_EXTRA_DATA_FIELD_MIN_SIZE_BYTES = 6; - - private static final short ANDROID_COMMON_PAGE_ALIGNMENT_BYTES = 4096; - - /** Name of the Android manifest ZIP entry in APKs. */ - private static final String ANDROID_MANIFEST_ZIP_ENTRY_NAME = "AndroidManifest.xml"; - - private final List mSignerConfigs; - private final SignerConfig mSourceStampSignerConfig; - private final SigningCertificateLineage mSourceStampSigningCertificateLineage; - private final boolean mForceSourceStampOverwrite; - private final Integer mMinSdkVersion; - private final boolean mV1SigningEnabled; - private final boolean mV2SigningEnabled; - private final boolean mV3SigningEnabled; - private final boolean mV4SigningEnabled; - private final boolean mVerityEnabled; - private final boolean mV4ErrorReportingEnabled; - private final boolean mDebuggableApkPermitted; - private final boolean mOtherSignersSignaturesPreserved; - private final String mCreatedBy; - - private final ApkSignerEngine mSignerEngine; - - private final File mInputApkFile; - private final DataSource mInputApkDataSource; - - private final File mOutputApkFile; - private final DataSink mOutputApkDataSink; - private final DataSource mOutputApkDataSource; - - private final File mOutputV4File; - - private final SigningCertificateLineage mSigningCertificateLineage; - - private ApkSigner( - List signerConfigs, - SignerConfig sourceStampSignerConfig, - SigningCertificateLineage sourceStampSigningCertificateLineage, - boolean forceSourceStampOverwrite, - Integer minSdkVersion, - boolean v1SigningEnabled, - boolean v2SigningEnabled, - boolean v3SigningEnabled, - boolean v4SigningEnabled, - boolean verityEnabled, - boolean v4ErrorReportingEnabled, - boolean debuggableApkPermitted, - boolean otherSignersSignaturesPreserved, - String createdBy, - ApkSignerEngine signerEngine, - File inputApkFile, - DataSource inputApkDataSource, - File outputApkFile, - DataSink outputApkDataSink, - DataSource outputApkDataSource, - File outputV4File, - SigningCertificateLineage signingCertificateLineage) { - - mSignerConfigs = signerConfigs; - mSourceStampSignerConfig = sourceStampSignerConfig; - mSourceStampSigningCertificateLineage = sourceStampSigningCertificateLineage; - mForceSourceStampOverwrite = forceSourceStampOverwrite; - mMinSdkVersion = minSdkVersion; - mV1SigningEnabled = v1SigningEnabled; - mV2SigningEnabled = v2SigningEnabled; - mV3SigningEnabled = v3SigningEnabled; - mV4SigningEnabled = v4SigningEnabled; - mVerityEnabled = verityEnabled; - mV4ErrorReportingEnabled = v4ErrorReportingEnabled; - mDebuggableApkPermitted = debuggableApkPermitted; - mOtherSignersSignaturesPreserved = otherSignersSignaturesPreserved; - mCreatedBy = createdBy; - - mSignerEngine = signerEngine; - - mInputApkFile = inputApkFile; - mInputApkDataSource = inputApkDataSource; - - mOutputApkFile = outputApkFile; - mOutputApkDataSink = outputApkDataSink; - mOutputApkDataSource = outputApkDataSource; - - mOutputV4File = outputV4File; - - mSigningCertificateLineage = signingCertificateLineage; - } - - /** - * Signs the input APK and outputs the resulting signed APK. The input APK is not modified. - * - * @throws IOException if an I/O error is encountered while reading or writing the APKs - * @throws ApkFormatException if the input APK is malformed - * @throws NoSuchAlgorithmException if the APK signatures cannot be produced or verified because - * a required cryptographic algorithm implementation is missing - * @throws InvalidKeyException if a signature could not be generated because a signing key is - * not suitable for generating the signature - * @throws SignatureException if an error occurred while generating or verifying a signature - * @throws IllegalStateException if this signer's configuration is missing required information - * or if the signing engine is in an invalid state. - */ - public void sign() - throws IOException, ApkFormatException, NoSuchAlgorithmException, InvalidKeyException, - SignatureException, IllegalStateException { - Closeable in = null; - DataSource inputApk; - try { - if (mInputApkDataSource != null) { - inputApk = mInputApkDataSource; - } else if (mInputApkFile != null) { - RandomAccessFile inputFile = new RandomAccessFile(mInputApkFile, "r"); - in = inputFile; - inputApk = DataSources.asDataSource(inputFile); - } else { - throw new IllegalStateException("Input APK not specified"); - } - - Closeable out = null; - try { - DataSink outputApkOut; - DataSource outputApkIn; - if (mOutputApkDataSink != null) { - outputApkOut = mOutputApkDataSink; - outputApkIn = mOutputApkDataSource; - } else if (mOutputApkFile != null) { - RandomAccessFile outputFile = new RandomAccessFile(mOutputApkFile, "rw"); - out = outputFile; - outputFile.setLength(0); - outputApkOut = DataSinks.asDataSink(outputFile); - outputApkIn = DataSources.asDataSource(outputFile); - } else { - throw new IllegalStateException("Output APK not specified"); - } - - sign(inputApk, outputApkOut, outputApkIn); - } finally { - if (out != null) { - out.close(); - } - } - } finally { - if (in != null) { - in.close(); - } - } - } - - private void sign(DataSource inputApk, DataSink outputApkOut, DataSource outputApkIn) - throws IOException, ApkFormatException, NoSuchAlgorithmException, InvalidKeyException, - SignatureException { - // Step 1. Find input APK's main ZIP sections - ApkUtils.ZipSections inputZipSections; - try { - inputZipSections = ApkUtils.findZipSections(inputApk); - } catch (ZipFormatException e) { - throw new ApkFormatException("Malformed APK: not a ZIP archive", e); - } - long inputApkSigningBlockOffset = -1; - DataSource inputApkSigningBlock = null; - try { - ApkUtils.ApkSigningBlock apkSigningBlockInfo = - ApkUtils.findApkSigningBlock(inputApk, inputZipSections); - inputApkSigningBlockOffset = apkSigningBlockInfo.getStartOffset(); - inputApkSigningBlock = apkSigningBlockInfo.getContents(); - } catch (ApkSigningBlockNotFoundException e) { - // Input APK does not contain an APK Signing Block. That's OK. APKs are not required to - // contain this block. It's only needed if the APK is signed using APK Signature Scheme - // v2 and/or v3. - } - DataSource inputApkLfhSection = - inputApk.slice( - 0, - (inputApkSigningBlockOffset != -1) - ? inputApkSigningBlockOffset - : inputZipSections.getZipCentralDirectoryOffset()); - - // Step 2. Parse the input APK's ZIP Central Directory - ByteBuffer inputCd = getZipCentralDirectory(inputApk, inputZipSections); - List inputCdRecords = - parseZipCentralDirectory(inputCd, inputZipSections); - - List pinPatterns = - extractPinPatterns(inputCdRecords, inputApkLfhSection); - List pinByteRanges = pinPatterns == null ? null : new ArrayList<>(); - - // Step 3. Obtain a signer engine instance - ApkSignerEngine signerEngine; - if (mSignerEngine != null) { - // Use the provided signer engine - signerEngine = mSignerEngine; - } else { - // Construct a signer engine from the provided parameters - int minSdkVersion; - if (mMinSdkVersion != null) { - // No need to extract minSdkVersion from the APK's AndroidManifest.xml - minSdkVersion = mMinSdkVersion; - } else { - // Need to extract minSdkVersion from the APK's AndroidManifest.xml - minSdkVersion = getMinSdkVersionFromApk(inputCdRecords, inputApkLfhSection); - } - List engineSignerConfigs = - new ArrayList<>(mSignerConfigs.size()); - for (SignerConfig signerConfig : mSignerConfigs) { - engineSignerConfigs.add( - new DefaultApkSignerEngine.SignerConfig.Builder( - signerConfig.getName(), - signerConfig.getPrivateKey(), - signerConfig.getCertificates(), - signerConfig.getDeterministicDsaSigning()) - .build()); - } - DefaultApkSignerEngine.Builder signerEngineBuilder = - new DefaultApkSignerEngine.Builder(engineSignerConfigs, minSdkVersion) - .setV1SigningEnabled(mV1SigningEnabled) - .setV2SigningEnabled(mV2SigningEnabled) - .setV3SigningEnabled(mV3SigningEnabled) - .setVerityEnabled(mVerityEnabled) - .setDebuggableApkPermitted(mDebuggableApkPermitted) - .setOtherSignersSignaturesPreserved(mOtherSignersSignaturesPreserved) - .setSigningCertificateLineage(mSigningCertificateLineage); - if (mCreatedBy != null) { - signerEngineBuilder.setCreatedBy(mCreatedBy); - } - if (mSourceStampSignerConfig != null) { - signerEngineBuilder.setStampSignerConfig( - new DefaultApkSignerEngine.SignerConfig.Builder( - mSourceStampSignerConfig.getName(), - mSourceStampSignerConfig.getPrivateKey(), - mSourceStampSignerConfig.getCertificates(), - mSourceStampSignerConfig.getDeterministicDsaSigning()) - .build()); - } - if (mSourceStampSigningCertificateLineage != null) { - signerEngineBuilder.setSourceStampSigningCertificateLineage( - mSourceStampSigningCertificateLineage); - } - signerEngine = signerEngineBuilder.build(); - } - - // Step 4. Provide the signer engine with the input APK's APK Signing Block (if any) - if (inputApkSigningBlock != null) { - signerEngine.inputApkSigningBlock(inputApkSigningBlock); - } - - // Step 5. Iterate over input APK's entries and output the Local File Header + data of those - // entries which need to be output. Entries are iterated in the order in which their Local - // File Header records are stored in the file. This is to achieve better data locality in - // case Central Directory entries are in the wrong order. - List inputCdRecordsSortedByLfhOffset = - new ArrayList<>(inputCdRecords); - Collections.sort( - inputCdRecordsSortedByLfhOffset, - CentralDirectoryRecord.BY_LOCAL_FILE_HEADER_OFFSET_COMPARATOR); - int lastModifiedDateForNewEntries = -1; - int lastModifiedTimeForNewEntries = -1; - long inputOffset = 0; - long outputOffset = 0; - byte[] sourceStampCertificateDigest = null; - Map outputCdRecordsByName = - new HashMap<>(inputCdRecords.size()); - for (final CentralDirectoryRecord inputCdRecord : inputCdRecordsSortedByLfhOffset) { - String entryName = inputCdRecord.getName(); - if (Hints.PIN_BYTE_RANGE_ZIP_ENTRY_NAME.equals(entryName)) { - continue; // We'll re-add below if needed. - } - if (SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME.equals(entryName)) { - try { - sourceStampCertificateDigest = - LocalFileRecord.getUncompressedData( - inputApkLfhSection, inputCdRecord, inputApkLfhSection.size()); - } catch (ZipFormatException ex) { - throw new ApkFormatException("Bad source stamp entry"); - } - continue; // Existing source stamp is handled below as needed. - } - ApkSignerEngine.InputJarEntryInstructions entryInstructions = - signerEngine.inputJarEntry(entryName); - boolean shouldOutput; - switch (entryInstructions.getOutputPolicy()) { - case OUTPUT: - shouldOutput = true; - break; - case OUTPUT_BY_ENGINE: - case SKIP: - shouldOutput = false; - break; - default: - throw new RuntimeException( - "Unknown output policy: " + entryInstructions.getOutputPolicy()); - } - - long inputLocalFileHeaderStartOffset = inputCdRecord.getLocalFileHeaderOffset(); - if (inputLocalFileHeaderStartOffset > inputOffset) { - // Unprocessed data in input starting at inputOffset and ending and the start of - // this record's LFH. We output this data verbatim because this signer is supposed - // to preserve as much of input as possible. - long chunkSize = inputLocalFileHeaderStartOffset - inputOffset; - inputApkLfhSection.feed(inputOffset, chunkSize, outputApkOut); - outputOffset += chunkSize; - inputOffset = inputLocalFileHeaderStartOffset; - } - LocalFileRecord inputLocalFileRecord; - try { - inputLocalFileRecord = - LocalFileRecord.getRecord( - inputApkLfhSection, inputCdRecord, inputApkLfhSection.size()); - } catch (ZipFormatException e) { - throw new ApkFormatException("Malformed ZIP entry: " + inputCdRecord.getName(), e); - } - inputOffset += inputLocalFileRecord.getSize(); - - ApkSignerEngine.InspectJarEntryRequest inspectEntryRequest = - entryInstructions.getInspectJarEntryRequest(); - if (inspectEntryRequest != null) { - fulfillInspectInputJarEntryRequest( - inputApkLfhSection, inputLocalFileRecord, inspectEntryRequest); - } - - if (shouldOutput) { - // Find the max value of last modified, to be used for new entries added by the - // signer. - int lastModifiedDate = inputCdRecord.getLastModificationDate(); - int lastModifiedTime = inputCdRecord.getLastModificationTime(); - if ((lastModifiedDateForNewEntries == -1) - || (lastModifiedDate > lastModifiedDateForNewEntries) - || ((lastModifiedDate == lastModifiedDateForNewEntries) - && (lastModifiedTime > lastModifiedTimeForNewEntries))) { - lastModifiedDateForNewEntries = lastModifiedDate; - lastModifiedTimeForNewEntries = lastModifiedTime; - } - - inspectEntryRequest = signerEngine.outputJarEntry(entryName); - if (inspectEntryRequest != null) { - fulfillInspectInputJarEntryRequest( - inputApkLfhSection, inputLocalFileRecord, inspectEntryRequest); - } - - // Output entry's Local File Header + data - long outputLocalFileHeaderOffset = outputOffset; - OutputSizeAndDataOffset outputLfrResult = - outputInputJarEntryLfhRecordPreservingDataAlignment( - inputApkLfhSection, - inputLocalFileRecord, - outputApkOut, - outputLocalFileHeaderOffset); - outputOffset += outputLfrResult.outputBytes; - long outputDataOffset = - outputLocalFileHeaderOffset + outputLfrResult.dataOffsetBytes; - - if (pinPatterns != null) { - boolean pinFileHeader = false; - for (Hints.PatternWithRange pinPattern : pinPatterns) { - if (pinPattern.matcher(inputCdRecord.getName()).matches()) { - Hints.ByteRange dataRange = - new Hints.ByteRange(outputDataOffset, outputOffset); - Hints.ByteRange pinRange = - pinPattern.ClampToAbsoluteByteRange(dataRange); - if (pinRange != null) { - pinFileHeader = true; - pinByteRanges.add(pinRange); - } - } - } - if (pinFileHeader) { - pinByteRanges.add( - new Hints.ByteRange(outputLocalFileHeaderOffset, outputDataOffset)); - } - } - - // Enqueue entry's Central Directory record for output - CentralDirectoryRecord outputCdRecord; - if (outputLocalFileHeaderOffset == inputLocalFileRecord.getStartOffsetInArchive()) { - outputCdRecord = inputCdRecord; - } else { - outputCdRecord = - inputCdRecord.createWithModifiedLocalFileHeaderOffset( - outputLocalFileHeaderOffset); - } - outputCdRecordsByName.put(entryName, outputCdRecord); - } - } - long inputLfhSectionSize = inputApkLfhSection.size(); - if (inputOffset < inputLfhSectionSize) { - // Unprocessed data in input starting at inputOffset and ending and the end of the input - // APK's LFH section. We output this data verbatim because this signer is supposed - // to preserve as much of input as possible. - long chunkSize = inputLfhSectionSize - inputOffset; - inputApkLfhSection.feed(inputOffset, chunkSize, outputApkOut); - outputOffset += chunkSize; - inputOffset = inputLfhSectionSize; - } - - // Step 6. Sort output APK's Central Directory records in the order in which they should - // appear in the output - List outputCdRecords = new ArrayList<>(inputCdRecords.size() + 10); - for (CentralDirectoryRecord inputCdRecord : inputCdRecords) { - String entryName = inputCdRecord.getName(); - CentralDirectoryRecord outputCdRecord = outputCdRecordsByName.get(entryName); - if (outputCdRecord != null) { - outputCdRecords.add(outputCdRecord); - } - } - - if (lastModifiedDateForNewEntries == -1) { - lastModifiedDateForNewEntries = 0x3a21; // Jan 1 2009 (DOS) - lastModifiedTimeForNewEntries = 0; - } - - // Step 7. Generate and output SourceStamp certificate hash, if necessary. This may output - // more Local File Header + data entries and add to the list of output Central Directory - // records. - if (signerEngine.isEligibleForSourceStamp()) { - byte[] uncompressedData = signerEngine.generateSourceStampCertificateDigest(); - if (mForceSourceStampOverwrite - || sourceStampCertificateDigest == null - || Arrays.equals(uncompressedData, sourceStampCertificateDigest)) { - outputOffset += - outputDataToOutputApk( - SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME, - uncompressedData, - outputOffset, - outputCdRecords, - lastModifiedTimeForNewEntries, - lastModifiedDateForNewEntries, - outputApkOut); - } else { - throw new ApkFormatException( - String.format( - "Cannot generate SourceStamp. APK contains an existing entry with" - + " the name: %s, and it is different than the provided source" - + " stamp certificate", - SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME)); - } - } - - // Step 7.5. Generate pinlist.meta file if necessary. - // This has to be before the step 8 so that the file is signed. - if (pinByteRanges != null) { - // Covers JAR signature and zip central dir entry. - // The signature files don't have to be pinned, but pinning them isn't that wasteful - // since the total size is small. - pinByteRanges.add(new Hints.ByteRange(outputOffset, Long.MAX_VALUE)); - String entryName = Hints.PIN_BYTE_RANGE_ZIP_ENTRY_NAME; - byte[] uncompressedData = Hints.encodeByteRangeList(pinByteRanges); - - requestOutputEntryInspection(signerEngine, entryName, uncompressedData); - outputOffset += - outputDataToOutputApk( - entryName, - uncompressedData, - outputOffset, - outputCdRecords, - lastModifiedTimeForNewEntries, - lastModifiedDateForNewEntries, - outputApkOut); - } - - // Step 8. Generate and output JAR signatures, if necessary. This may output more Local File - // Header + data entries and add to the list of output Central Directory records. - ApkSignerEngine.OutputJarSignatureRequest outputJarSignatureRequest = - signerEngine.outputJarEntries(); - if (outputJarSignatureRequest != null) { - for (ApkSignerEngine.OutputJarSignatureRequest.JarEntry entry : - outputJarSignatureRequest.getAdditionalJarEntries()) { - String entryName = entry.getName(); - byte[] uncompressedData = entry.getData(); - - requestOutputEntryInspection(signerEngine, entryName, uncompressedData); - outputOffset += - outputDataToOutputApk( - entryName, - uncompressedData, - outputOffset, - outputCdRecords, - lastModifiedTimeForNewEntries, - lastModifiedDateForNewEntries, - outputApkOut); - } - outputJarSignatureRequest.done(); - } - - // Step 9. Construct output ZIP Central Directory in an in-memory buffer - long outputCentralDirSizeBytes = 0; - for (CentralDirectoryRecord record : outputCdRecords) { - outputCentralDirSizeBytes += record.getSize(); - } - if (outputCentralDirSizeBytes > Integer.MAX_VALUE) { - throw new IOException( - "Output ZIP Central Directory too large: " - + outputCentralDirSizeBytes - + " bytes"); - } - ByteBuffer outputCentralDir = ByteBuffer.allocate((int) outputCentralDirSizeBytes); - for (CentralDirectoryRecord record : outputCdRecords) { - record.copyTo(outputCentralDir); - } - outputCentralDir.flip(); - DataSource outputCentralDirDataSource = new ByteBufferDataSource(outputCentralDir); - long outputCentralDirStartOffset = outputOffset; - int outputCentralDirRecordCount = outputCdRecords.size(); - - // Step 10. Construct output ZIP End of Central Directory record in an in-memory buffer - ByteBuffer outputEocd = - EocdRecord.createWithModifiedCentralDirectoryInfo( - inputZipSections.getZipEndOfCentralDirectory(), - outputCentralDirRecordCount, - outputCentralDirDataSource.size(), - outputCentralDirStartOffset); - - // Step 11. Generate and output APK Signature Scheme v2 and/or v3 signatures and/or - // SourceStamp signatures, if necessary. - // This may insert an APK Signing Block just before the output's ZIP Central Directory - ApkSignerEngine.OutputApkSigningBlockRequest2 outputApkSigningBlockRequest = - signerEngine.outputZipSections2( - outputApkIn, - outputCentralDirDataSource, - DataSources.asDataSource(outputEocd)); - - if (outputApkSigningBlockRequest != null) { - int padding = outputApkSigningBlockRequest.getPaddingSizeBeforeApkSigningBlock(); - outputApkOut.consume(ByteBuffer.allocate(padding)); - byte[] outputApkSigningBlock = outputApkSigningBlockRequest.getApkSigningBlock(); - outputApkOut.consume(outputApkSigningBlock, 0, outputApkSigningBlock.length); - ZipUtils.setZipEocdCentralDirectoryOffset( - outputEocd, - outputCentralDirStartOffset + padding + outputApkSigningBlock.length); - outputApkSigningBlockRequest.done(); - } - - // Step 12. Output ZIP Central Directory and ZIP End of Central Directory - outputCentralDirDataSource.feed(0, outputCentralDirDataSource.size(), outputApkOut); - outputApkOut.consume(outputEocd); - signerEngine.outputDone(); - - // Step 13. Generate and output APK Signature Scheme v4 signatures, if necessary. - if (mV4SigningEnabled) { - signerEngine.signV4(outputApkIn, mOutputV4File, !mV4ErrorReportingEnabled); - } - } - - private static void requestOutputEntryInspection( - ApkSignerEngine signerEngine, - String entryName, - byte[] uncompressedData) - throws IOException { - ApkSignerEngine.InspectJarEntryRequest inspectEntryRequest = - signerEngine.outputJarEntry(entryName); - if (inspectEntryRequest != null) { - inspectEntryRequest.getDataSink().consume( - uncompressedData, 0, uncompressedData.length); - inspectEntryRequest.done(); - } - } - - private static long outputDataToOutputApk( - String entryName, - byte[] uncompressedData, - long localFileHeaderOffset, - List outputCdRecords, - int lastModifiedTimeForNewEntries, - int lastModifiedDateForNewEntries, - DataSink outputApkOut) - throws IOException { - ZipUtils.DeflateResult deflateResult = ZipUtils.deflate(ByteBuffer.wrap(uncompressedData)); - byte[] compressedData = deflateResult.output; - long uncompressedDataCrc32 = deflateResult.inputCrc32; - long numOfDataBytes = - LocalFileRecord.outputRecordWithDeflateCompressedData( - entryName, - lastModifiedTimeForNewEntries, - lastModifiedDateForNewEntries, - compressedData, - uncompressedDataCrc32, - uncompressedData.length, - outputApkOut); - outputCdRecords.add( - CentralDirectoryRecord.createWithDeflateCompressedData( - entryName, - lastModifiedTimeForNewEntries, - lastModifiedDateForNewEntries, - uncompressedDataCrc32, - compressedData.length, - uncompressedData.length, - localFileHeaderOffset)); - return numOfDataBytes; - } - - private static void fulfillInspectInputJarEntryRequest( - DataSource lfhSection, - LocalFileRecord localFileRecord, - ApkSignerEngine.InspectJarEntryRequest inspectEntryRequest) - throws IOException, ApkFormatException { - try { - localFileRecord.outputUncompressedData(lfhSection, inspectEntryRequest.getDataSink()); - } catch (ZipFormatException e) { - throw new ApkFormatException("Malformed ZIP entry: " + localFileRecord.getName(), e); - } - inspectEntryRequest.done(); - } - - private static class OutputSizeAndDataOffset { - public long outputBytes; - public long dataOffsetBytes; - - public OutputSizeAndDataOffset(long outputBytes, long dataOffsetBytes) { - this.outputBytes = outputBytes; - this.dataOffsetBytes = dataOffsetBytes; - } - } - - private static OutputSizeAndDataOffset outputInputJarEntryLfhRecordPreservingDataAlignment( - DataSource inputLfhSection, - LocalFileRecord inputRecord, - DataSink outputLfhSection, - long outputOffset) - throws IOException { - long inputOffset = inputRecord.getStartOffsetInArchive(); - if (inputOffset == outputOffset) { - // This record's data will be aligned same as in the input APK. - return new OutputSizeAndDataOffset( - inputRecord.outputRecord(inputLfhSection, outputLfhSection), - inputRecord.getDataStartOffsetInRecord()); - } - int dataAlignmentMultiple = getInputJarEntryDataAlignmentMultiple(inputRecord); - if ((dataAlignmentMultiple <= 1) - || ((inputOffset % dataAlignmentMultiple) - == (outputOffset % dataAlignmentMultiple))) { - // This record's data will be aligned same as in the input APK. - return new OutputSizeAndDataOffset( - inputRecord.outputRecord(inputLfhSection, outputLfhSection), - inputRecord.getDataStartOffsetInRecord()); - } - - long inputDataStartOffset = inputOffset + inputRecord.getDataStartOffsetInRecord(); - if ((inputDataStartOffset % dataAlignmentMultiple) != 0) { - // This record's data is not aligned in the input APK. No need to align it in the - // output. - return new OutputSizeAndDataOffset( - inputRecord.outputRecord(inputLfhSection, outputLfhSection), - inputRecord.getDataStartOffsetInRecord()); - } - - // This record's data needs to be re-aligned in the output. This is achieved using the - // record's extra field. - ByteBuffer aligningExtra = - createExtraFieldToAlignData( - inputRecord.getExtra(), - outputOffset + inputRecord.getExtraFieldStartOffsetInsideRecord(), - dataAlignmentMultiple); - long dataOffset = - (long) inputRecord.getDataStartOffsetInRecord() - + aligningExtra.remaining() - - inputRecord.getExtra().remaining(); - return new OutputSizeAndDataOffset( - inputRecord.outputRecordWithModifiedExtra( - inputLfhSection, aligningExtra, outputLfhSection), - dataOffset); - } - - private static int getInputJarEntryDataAlignmentMultiple(LocalFileRecord entry) { - if (entry.isDataCompressed()) { - // Compressed entries don't need to be aligned - return 1; - } - - // Attempt to obtain the alignment multiple from the entry's extra field. - ByteBuffer extra = entry.getExtra(); - if (extra.hasRemaining()) { - extra.order(ByteOrder.LITTLE_ENDIAN); - // FORMAT: sequence of fields. Each field consists of: - // * uint16 ID - // * uint16 size - // * 'size' bytes: payload - while (extra.remaining() >= 4) { - short headerId = extra.getShort(); - int dataSize = ZipUtils.getUnsignedInt16(extra); - if (dataSize > extra.remaining()) { - // Malformed field -- insufficient input remaining - break; - } - if (headerId != ALIGNMENT_ZIP_EXTRA_DATA_FIELD_HEADER_ID) { - // Skip this field - extra.position(extra.position() + dataSize); - continue; - } - // This is APK alignment field. - // FORMAT: - // * uint16 alignment multiple (in bytes) - // * remaining bytes -- padding to achieve alignment of data which starts after - // the extra field - if (dataSize < 2) { - // Malformed - break; - } - return ZipUtils.getUnsignedInt16(extra); - } - } - - // Fall back to filename-based defaults - return (entry.getName().endsWith(".so")) ? ANDROID_COMMON_PAGE_ALIGNMENT_BYTES : 4; - } - - private static ByteBuffer createExtraFieldToAlignData( - ByteBuffer original, long extraStartOffset, int dataAlignmentMultiple) { - if (dataAlignmentMultiple <= 1) { - return original; - } - - // In the worst case scenario, we'll increase the output size by 6 + dataAlignment - 1. - ByteBuffer result = ByteBuffer.allocate(original.remaining() + 5 + dataAlignmentMultiple); - result.order(ByteOrder.LITTLE_ENDIAN); - - // Step 1. Output all extra fields other than the one which is to do with alignment - // FORMAT: sequence of fields. Each field consists of: - // * uint16 ID - // * uint16 size - // * 'size' bytes: payload - while (original.remaining() >= 4) { - short headerId = original.getShort(); - int dataSize = ZipUtils.getUnsignedInt16(original); - if (dataSize > original.remaining()) { - // Malformed field -- insufficient input remaining - break; - } - if (((headerId == 0) && (dataSize == 0)) - || (headerId == ALIGNMENT_ZIP_EXTRA_DATA_FIELD_HEADER_ID)) { - // Ignore the field if it has to do with the old APK data alignment method (filling - // the extra field with 0x00 bytes) or the new APK data alignment method. - original.position(original.position() + dataSize); - continue; - } - // Copy this field (including header) to the output - original.position(original.position() - 4); - int originalLimit = original.limit(); - original.limit(original.position() + 4 + dataSize); - result.put(original); - original.limit(originalLimit); - } - - // Step 2. Add alignment field - // FORMAT: - // * uint16 extra header ID - // * uint16 extra data size - // Payload ('data size' bytes) - // * uint16 alignment multiple (in bytes) - // * remaining bytes -- padding to achieve alignment of data which starts after the - // extra field - long dataMinStartOffset = - extraStartOffset - + result.position() - + ALIGNMENT_ZIP_EXTRA_DATA_FIELD_MIN_SIZE_BYTES; - int paddingSizeBytes = - (dataAlignmentMultiple - ((int) (dataMinStartOffset % dataAlignmentMultiple))) - % dataAlignmentMultiple; - result.putShort(ALIGNMENT_ZIP_EXTRA_DATA_FIELD_HEADER_ID); - ZipUtils.putUnsignedInt16(result, 2 + paddingSizeBytes); - ZipUtils.putUnsignedInt16(result, dataAlignmentMultiple); - result.position(result.position() + paddingSizeBytes); - result.flip(); - - return result; - } - - private static ByteBuffer getZipCentralDirectory( - DataSource apk, ApkUtils.ZipSections apkSections) - throws IOException, ApkFormatException { - long cdSizeBytes = apkSections.getZipCentralDirectorySizeBytes(); - if (cdSizeBytes > Integer.MAX_VALUE) { - throw new ApkFormatException("ZIP Central Directory too large: " + cdSizeBytes); - } - long cdOffset = apkSections.getZipCentralDirectoryOffset(); - ByteBuffer cd = apk.getByteBuffer(cdOffset, (int) cdSizeBytes); - cd.order(ByteOrder.LITTLE_ENDIAN); - return cd; - } - - private static List parseZipCentralDirectory( - ByteBuffer cd, ApkUtils.ZipSections apkSections) throws ApkFormatException { - long cdOffset = apkSections.getZipCentralDirectoryOffset(); - int expectedCdRecordCount = apkSections.getZipCentralDirectoryRecordCount(); - List cdRecords = new ArrayList<>(expectedCdRecordCount); - Set entryNames = new HashSet<>(expectedCdRecordCount); - for (int i = 0; i < expectedCdRecordCount; i++) { - CentralDirectoryRecord cdRecord; - int offsetInsideCd = cd.position(); - try { - cdRecord = CentralDirectoryRecord.getRecord(cd); - } catch (ZipFormatException e) { - throw new ApkFormatException( - "Malformed ZIP Central Directory record #" - + (i + 1) - + " at file offset " - + (cdOffset + offsetInsideCd), - e); - } - String entryName = cdRecord.getName(); - if (!entryNames.add(entryName)) { - throw new ApkFormatException( - "Multiple ZIP entries with the same name: " + entryName); - } - cdRecords.add(cdRecord); - } - if (cd.hasRemaining()) { - throw new ApkFormatException( - "Unused space at the end of ZIP Central Directory: " - + cd.remaining() - + " bytes starting at file offset " - + (cdOffset + cd.position())); - } - - return cdRecords; - } - - private static CentralDirectoryRecord findCdRecord( - List cdRecords, String name) { - for (CentralDirectoryRecord cdRecord : cdRecords) { - if (name.equals(cdRecord.getName())) { - return cdRecord; - } - } - return null; - } - - /** - * Returns the contents of the APK's {@code AndroidManifest.xml} or {@code null} if this entry - * is not present in the APK. - */ - static ByteBuffer getAndroidManifestFromApk( - List cdRecords, DataSource lhfSection) - throws IOException, ApkFormatException, ZipFormatException { - CentralDirectoryRecord androidManifestCdRecord = - findCdRecord(cdRecords, ANDROID_MANIFEST_ZIP_ENTRY_NAME); - if (androidManifestCdRecord == null) { - throw new ApkFormatException("Missing " + ANDROID_MANIFEST_ZIP_ENTRY_NAME); - } - - return ByteBuffer.wrap( - LocalFileRecord.getUncompressedData( - lhfSection, androidManifestCdRecord, lhfSection.size())); - } - - /** - * Return list of pin patterns embedded in the pin pattern asset file. If no such file, return - * {@code null}. - */ - private static List extractPinPatterns( - List cdRecords, DataSource lhfSection) - throws IOException, ApkFormatException { - CentralDirectoryRecord pinListCdRecord = - findCdRecord(cdRecords, Hints.PIN_HINT_ASSET_ZIP_ENTRY_NAME); - List pinPatterns = null; - if (pinListCdRecord != null) { - pinPatterns = new ArrayList<>(); - byte[] patternBlob; - try { - patternBlob = - LocalFileRecord.getUncompressedData( - lhfSection, pinListCdRecord, lhfSection.size()); - } catch (ZipFormatException ex) { - throw new ApkFormatException("Bad " + pinListCdRecord); - } - pinPatterns = Hints.parsePinPatterns(patternBlob); - } - return pinPatterns; - } - - /** - * Returns the minimum Android version (API Level) supported by the provided APK. This is based - * on the {@code android:minSdkVersion} attributes of the APK's {@code AndroidManifest.xml}. - */ - private static int getMinSdkVersionFromApk( - List cdRecords, DataSource lhfSection) - throws IOException, MinSdkVersionException { - ByteBuffer androidManifest; - try { - androidManifest = getAndroidManifestFromApk(cdRecords, lhfSection); - } catch (ZipFormatException | ApkFormatException e) { - throw new MinSdkVersionException( - "Failed to determine APK's minimum supported Android platform version", e); - } - return ApkUtils.getMinSdkVersionFromBinaryAndroidManifest(androidManifest); - } - - /** - * Configuration of a signer. - * - *

Use {@link Builder} to obtain configuration instances. - */ - public static class SignerConfig { - private final String mName; - private final PrivateKey mPrivateKey; - private final List mCertificates; - private boolean mDeterministicDsaSigning; - - private SignerConfig( - String name, - PrivateKey privateKey, - List certificates, - boolean deterministicDsaSigning) { - mName = name; - mPrivateKey = privateKey; - mCertificates = Collections.unmodifiableList(new ArrayList<>(certificates)); - mDeterministicDsaSigning = deterministicDsaSigning; - } - /** Returns the name of this signer. */ - public String getName() { - return mName; - } - - /** Returns the signing key of this signer. */ - public PrivateKey getPrivateKey() { - return mPrivateKey; - } - - /** - * Returns the certificate(s) of this signer. The first certificate's public key corresponds - * to this signer's private key. - */ - public List getCertificates() { - return mCertificates; - } - - - /** - * If this signer is a DSA signer, whether or not the signing is done deterministically. - */ - public boolean getDeterministicDsaSigning() { - return mDeterministicDsaSigning; - } - - /** Builder of {@link SignerConfig} instances. */ - public static class Builder { - private final String mName; - private final PrivateKey mPrivateKey; - private final List mCertificates; - private final boolean mDeterministicDsaSigning; - - /** - * Constructs a new {@code Builder}. - * - * @param name signer's name. The name is reflected in the name of files comprising the - * JAR signature of the APK. - * @param privateKey signing key - * @param certificates list of one or more X.509 certificates. The subject public key of - * the first certificate must correspond to the {@code privateKey}. - */ - public Builder( - String name, - PrivateKey privateKey, - List certificates) { - this(name, privateKey, certificates, false); - } - - /** - * Constructs a new {@code Builder}. - * - * @param name signer's name. The name is reflected in the name of files comprising the - * JAR signature of the APK. - * @param privateKey signing key - * @param certificates list of one or more X.509 certificates. The subject public key of - * the first certificate must correspond to the {@code privateKey}. - * @param deterministicDsaSigning When signing using DSA, whether or not the - * deterministic variant (RFC6979) should be used. - */ - public Builder( - String name, - PrivateKey privateKey, - List certificates, - boolean deterministicDsaSigning) { - if (name.isEmpty()) { - throw new IllegalArgumentException("Empty name"); - } - mName = name; - mPrivateKey = privateKey; - mCertificates = new ArrayList<>(certificates); - mDeterministicDsaSigning = deterministicDsaSigning; - } - - /** - * Returns a new {@code SignerConfig} instance configured based on the configuration of - * this builder. - */ - public SignerConfig build() { - return new SignerConfig(mName, mPrivateKey, mCertificates, - mDeterministicDsaSigning); - } - } - } - - /** - * Builder of {@link ApkSigner} instances. - * - *

The builder requires the following information to construct a working {@code ApkSigner}: - * - *

- */ - public static class Builder { - private final List mSignerConfigs; - private SignerConfig mSourceStampSignerConfig; - private SigningCertificateLineage mSourceStampSigningCertificateLineage; - private boolean mForceSourceStampOverwrite = false; - private boolean mV1SigningEnabled = true; - private boolean mV2SigningEnabled = true; - private boolean mV3SigningEnabled = true; - private boolean mV4SigningEnabled = true; - private boolean mVerityEnabled = false; - private boolean mV4ErrorReportingEnabled = false; - private boolean mDebuggableApkPermitted = true; - private boolean mOtherSignersSignaturesPreserved; - private String mCreatedBy; - private Integer mMinSdkVersion; - - private final ApkSignerEngine mSignerEngine; - - private File mInputApkFile; - private DataSource mInputApkDataSource; - - private File mOutputApkFile; - private DataSink mOutputApkDataSink; - private DataSource mOutputApkDataSource; - - private File mOutputV4File; - - private SigningCertificateLineage mSigningCertificateLineage; - - // APK Signature Scheme v3 only supports a single signing certificate, so to move to v3 - // signing by default, but not require prior clients to update to explicitly disable v3 - // signing for multiple signers, we modify the mV3SigningEnabled depending on the provided - // inputs (multiple signers and mSigningCertificateLineage in particular). Maintain two - // extra variables to record whether or not mV3SigningEnabled has been set directly by a - // client and so should override the default behavior. - private boolean mV3SigningExplicitlyDisabled = false; - private boolean mV3SigningExplicitlyEnabled = false; - - /** - * Constructs a new {@code Builder} for an {@code ApkSigner} which signs using the provided - * signer configurations. The resulting signer may be further customized through this - * builder's setters, such as {@link #setMinSdkVersion(int)}, {@link - * #setV1SigningEnabled(boolean)}, {@link #setV2SigningEnabled(boolean)}, {@link - * #setOtherSignersSignaturesPreserved(boolean)}, {@link #setCreatedBy(String)}. - * - *

{@link #Builder(ApkSignerEngine)} is an alternative for advanced use cases where more - * control over low-level details of signing is desired. - */ - public Builder(List signerConfigs) { - if (signerConfigs.isEmpty()) { - throw new IllegalArgumentException("At least one signer config must be provided"); - } - if (signerConfigs.size() > 1) { - // APK Signature Scheme v3 only supports single signer, unless a - // SigningCertificateLineage is provided, in which case this will be reset to true, - // since we don't yet have a v4 scheme about which to worry - mV3SigningEnabled = false; - } - mSignerConfigs = new ArrayList<>(signerConfigs); - mSignerEngine = null; - } - - /** - * Constructs a new {@code Builder} for an {@code ApkSigner} which signs using the provided - * signing engine. This is meant for advanced use cases where more control is needed over - * the lower-level details of signing. For typical use cases, {@link #Builder(List)} is more - * appropriate. - */ - public Builder(ApkSignerEngine signerEngine) { - if (signerEngine == null) { - throw new NullPointerException("signerEngine == null"); - } - mSignerEngine = signerEngine; - mSignerConfigs = null; - } - - /** Sets the signing configuration of the source stamp to be embedded in the APK. */ - public Builder setSourceStampSignerConfig(SignerConfig sourceStampSignerConfig) { - mSourceStampSignerConfig = sourceStampSignerConfig; - return this; - } - - /** - * Sets the source stamp {@link SigningCertificateLineage}. This structure provides proof of - * signing certificate rotation for certificates previously used to sign source stamps. - */ - public Builder setSourceStampSigningCertificateLineage( - SigningCertificateLineage sourceStampSigningCertificateLineage) { - mSourceStampSigningCertificateLineage = sourceStampSigningCertificateLineage; - return this; - } - - /** - * Sets whether the APK should overwrite existing source stamp, if found. - * - * @param force {@code true} to require the APK to be overwrite existing source stamp - */ - public Builder setForceSourceStampOverwrite(boolean force) { - mForceSourceStampOverwrite = force; - return this; - } - - /** - * Sets the APK to be signed. - * - * @see #setInputApk(DataSource) - */ - public Builder setInputApk(File inputApk) { - if (inputApk == null) { - throw new NullPointerException("inputApk == null"); - } - mInputApkFile = inputApk; - mInputApkDataSource = null; - return this; - } - - /** - * Sets the APK to be signed. - * - * @see #setInputApk(File) - */ - public Builder setInputApk(DataSource inputApk) { - if (inputApk == null) { - throw new NullPointerException("inputApk == null"); - } - mInputApkDataSource = inputApk; - mInputApkFile = null; - return this; - } - - /** - * Sets the location of the output (signed) APK. {@code ApkSigner} will create this file if - * it doesn't exist. - * - * @see #setOutputApk(ReadableDataSink) - * @see #setOutputApk(DataSink, DataSource) - */ - public Builder setOutputApk(File outputApk) { - if (outputApk == null) { - throw new NullPointerException("outputApk == null"); - } - mOutputApkFile = outputApk; - mOutputApkDataSink = null; - mOutputApkDataSource = null; - return this; - } - - /** - * Sets the readable data sink which will receive the output (signed) APK. After signing, - * the contents of the output APK will be available via the {@link DataSource} interface of - * the sink. - * - *

This variant of {@code setOutputApk} is useful for avoiding writing the output APK to - * a file. For example, an in-memory data sink, such as {@link - * DataSinks#newInMemoryDataSink()}, could be used instead of a file. - * - * @see #setOutputApk(File) - * @see #setOutputApk(DataSink, DataSource) - */ - public Builder setOutputApk(ReadableDataSink outputApk) { - if (outputApk == null) { - throw new NullPointerException("outputApk == null"); - } - return setOutputApk(outputApk, outputApk); - } - - /** - * Sets the sink which will receive the output (signed) APK. Data received by the {@code - * outputApkOut} sink must be visible through the {@code outputApkIn} data source. - * - *

This is an advanced variant of {@link #setOutputApk(ReadableDataSink)}, enabling the - * sink and the source to be different objects. - * - * @see #setOutputApk(ReadableDataSink) - * @see #setOutputApk(File) - */ - public Builder setOutputApk(DataSink outputApkOut, DataSource outputApkIn) { - if (outputApkOut == null) { - throw new NullPointerException("outputApkOut == null"); - } - if (outputApkIn == null) { - throw new NullPointerException("outputApkIn == null"); - } - mOutputApkFile = null; - mOutputApkDataSink = outputApkOut; - mOutputApkDataSource = outputApkIn; - return this; - } - - /** - * Sets the location of the V4 output file. {@code ApkSigner} will create this file if it - * doesn't exist. - */ - public Builder setV4SignatureOutputFile(File v4SignatureOutputFile) { - if (v4SignatureOutputFile == null) { - throw new NullPointerException("v4HashRootOutputFile == null"); - } - mOutputV4File = v4SignatureOutputFile; - return this; - } - - /** - * Sets the minimum Android platform version (API Level) on which APK signatures produced by - * the signer being built must verify. This method is useful for overriding the default - * behavior where the minimum API Level is obtained from the {@code android:minSdkVersion} - * attribute of the APK's {@code AndroidManifest.xml}. - * - *

Note: This method may result in APK signatures which don't verify on some - * Android platform versions supported by the APK. - * - *

Note: This method may only be invoked when this builder is not initialized - * with an {@link ApkSignerEngine}. - * - * @throws IllegalStateException if this builder was initialized with an {@link - * ApkSignerEngine} - */ - public Builder setMinSdkVersion(int minSdkVersion) { - checkInitializedWithoutEngine(); - mMinSdkVersion = minSdkVersion; - return this; - } - - /** - * Sets whether the APK should be signed using JAR signing (aka v1 signature scheme). - * - *

By default, whether APK is signed using JAR signing is determined by {@code - * ApkSigner}, based on the platform versions supported by the APK or specified using {@link - * #setMinSdkVersion(int)}. Disabling JAR signing will result in APK signatures which don't - * verify on Android Marshmallow (Android 6.0, API Level 23) and lower. - * - *

Note: This method may only be invoked when this builder is not initialized - * with an {@link ApkSignerEngine}. - * - * @param enabled {@code true} to require the APK to be signed using JAR signing, {@code - * false} to require the APK to not be signed using JAR signing. - * @throws IllegalStateException if this builder was initialized with an {@link - * ApkSignerEngine} - * @see JAR - * signing - */ - public Builder setV1SigningEnabled(boolean enabled) { - checkInitializedWithoutEngine(); - mV1SigningEnabled = enabled; - return this; - } - - /** - * Sets whether the APK should be signed using APK Signature Scheme v2 (aka v2 signature - * scheme). - * - *

By default, whether APK is signed using APK Signature Scheme v2 is determined by - * {@code ApkSigner} based on the platform versions supported by the APK or specified using - * {@link #setMinSdkVersion(int)}. - * - *

Note: This method may only be invoked when this builder is not initialized - * with an {@link ApkSignerEngine}. - * - * @param enabled {@code true} to require the APK to be signed using APK Signature Scheme - * v2, {@code false} to require the APK to not be signed using APK Signature Scheme v2. - * @throws IllegalStateException if this builder was initialized with an {@link - * ApkSignerEngine} - * @see APK Signature - * Scheme v2 - */ - public Builder setV2SigningEnabled(boolean enabled) { - checkInitializedWithoutEngine(); - mV2SigningEnabled = enabled; - return this; - } - - /** - * Sets whether the APK should be signed using APK Signature Scheme v3 (aka v3 signature - * scheme). - * - *

By default, whether APK is signed using APK Signature Scheme v3 is determined by - * {@code ApkSigner} based on the platform versions supported by the APK or specified using - * {@link #setMinSdkVersion(int)}. - * - *

Note: This method may only be invoked when this builder is not initialized - * with an {@link ApkSignerEngine}. - * - *

Note: APK Signature Scheme v3 only supports a single signing certificate, but - * may take multiple signers mapping to different targeted platform versions. - * - * @param enabled {@code true} to require the APK to be signed using APK Signature Scheme - * v3, {@code false} to require the APK to not be signed using APK Signature Scheme v3. - * @throws IllegalStateException if this builder was initialized with an {@link - * ApkSignerEngine} - */ - public Builder setV3SigningEnabled(boolean enabled) { - checkInitializedWithoutEngine(); - mV3SigningEnabled = enabled; - if (enabled) { - mV3SigningExplicitlyEnabled = true; - } else { - mV3SigningExplicitlyDisabled = true; - } - return this; - } - - /** - * Sets whether the APK should be signed using APK Signature Scheme v4. - * - *

V4 signing requires that the APK be v2 or v3 signed. - * - * @param enabled {@code true} to require the APK to be signed using APK Signature Scheme v2 - * or v3 and generate an v4 signature file - */ - public Builder setV4SigningEnabled(boolean enabled) { - checkInitializedWithoutEngine(); - mV4SigningEnabled = enabled; - mV4ErrorReportingEnabled = enabled; - return this; - } - - /** - * Sets whether errors during v4 signing should be reported and halt the signing process. - * - *

Error reporting for v4 signing is disabled by default, but will be enabled if the - * caller invokes {@link #setV4SigningEnabled} with a value of true. This method is useful - * for tools that enable v4 signing by default but don't want to fail the signing process if - * the user did not explicitly request the v4 signing. - * - * @param enabled {@code false} to prevent errors encountered during the V4 signing from - * halting the signing process - */ - public Builder setV4ErrorReportingEnabled(boolean enabled) { - checkInitializedWithoutEngine(); - mV4ErrorReportingEnabled = enabled; - return this; - } - - /** - * Sets whether to enable the verity signature algorithm for the v2 and v3 signature - * schemes. - * - * @param enabled {@code true} to enable the verity signature algorithm for inclusion in the - * v2 and v3 signature blocks. - */ - public Builder setVerityEnabled(boolean enabled) { - checkInitializedWithoutEngine(); - mVerityEnabled = enabled; - return this; - } - - /** - * Sets whether the APK should be signed even if it is marked as debuggable ({@code - * android:debuggable="true"} in its {@code AndroidManifest.xml}). For backward - * compatibility reasons, the default value of this setting is {@code true}. - * - *

It is dangerous to sign debuggable APKs with production/release keys because Android - * platform loosens security checks for such APKs. For example, arbitrary unauthorized code - * may be executed in the context of such an app by anybody with ADB shell access. - * - *

Note: This method may only be invoked when this builder is not initialized - * with an {@link ApkSignerEngine}. - */ - public Builder setDebuggableApkPermitted(boolean permitted) { - checkInitializedWithoutEngine(); - mDebuggableApkPermitted = permitted; - return this; - } - - /** - * Sets whether signatures produced by signers other than the ones configured in this engine - * should be copied from the input APK to the output APK. - * - *

By default, signatures of other signers are omitted from the output APK. - * - *

Note: This method may only be invoked when this builder is not initialized - * with an {@link ApkSignerEngine}. - * - * @throws IllegalStateException if this builder was initialized with an {@link - * ApkSignerEngine} - */ - public Builder setOtherSignersSignaturesPreserved(boolean preserved) { - checkInitializedWithoutEngine(); - mOtherSignersSignaturesPreserved = preserved; - return this; - } - - /** - * Sets the value of the {@code Created-By} field in JAR signature files. - * - *

Note: This method may only be invoked when this builder is not initialized - * with an {@link ApkSignerEngine}. - * - * @throws IllegalStateException if this builder was initialized with an {@link - * ApkSignerEngine} - */ - public Builder setCreatedBy(String createdBy) { - checkInitializedWithoutEngine(); - if (createdBy == null) { - throw new NullPointerException(); - } - mCreatedBy = createdBy; - return this; - } - - private void checkInitializedWithoutEngine() { - if (mSignerEngine != null) { - throw new IllegalStateException( - "Operation is not available when builder initialized with an engine"); - } - } - - /** - * Sets the {@link SigningCertificateLineage} to use with the v3 signature scheme. This - * structure provides proof of signing certificate rotation linking {@link SignerConfig} - * objects to previous ones. - */ - public Builder setSigningCertificateLineage( - SigningCertificateLineage signingCertificateLineage) { - if (signingCertificateLineage != null) { - mV3SigningEnabled = true; - mSigningCertificateLineage = signingCertificateLineage; - } - return this; - } - - /** - * Returns a new {@code ApkSigner} instance initialized according to the configuration of - * this builder. - */ - public ApkSigner build() { - if (mV3SigningExplicitlyDisabled && mV3SigningExplicitlyEnabled) { - throw new IllegalStateException( - "Builder configured to both enable and disable APK " - + "Signature Scheme v3 signing"); - } - - if (mV3SigningExplicitlyDisabled) { - mV3SigningEnabled = false; - } - - if (mV3SigningExplicitlyEnabled) { - mV3SigningEnabled = true; - } - - // If V4 signing is not explicitly set, and V2/V3 signing is disabled, then V4 signing - // must be disabled as well as it is dependent on V2/V3. - if (mV4SigningEnabled && !mV2SigningEnabled && !mV3SigningEnabled) { - if (!mV4ErrorReportingEnabled) { - mV4SigningEnabled = false; - } else { - throw new IllegalStateException( - "APK Signature Scheme v4 signing requires at least " - + "v2 or v3 signing to be enabled"); - } - } - - // TODO - if v3 signing is enabled, check provided signers and history to see if valid - - return new ApkSigner( - mSignerConfigs, - mSourceStampSignerConfig, - mSourceStampSigningCertificateLineage, - mForceSourceStampOverwrite, - mMinSdkVersion, - mV1SigningEnabled, - mV2SigningEnabled, - mV3SigningEnabled, - mV4SigningEnabled, - mVerityEnabled, - mV4ErrorReportingEnabled, - mDebuggableApkPermitted, - mOtherSignersSignaturesPreserved, - mCreatedBy, - mSignerEngine, - mInputApkFile, - mInputApkDataSource, - mOutputApkFile, - mOutputApkDataSink, - mOutputApkDataSource, - mOutputV4File, - mSigningCertificateLineage); - } - } -} diff --git a/app/src/main/java/com/android/apksig/ApkSignerEngine.java b/app/src/main/java/com/android/apksig/ApkSignerEngine.java deleted file mode 100644 index c79f232707..0000000000 --- a/app/src/main/java/com/android/apksig/ApkSignerEngine.java +++ /dev/null @@ -1,550 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig; - -import com.android.apksig.apk.ApkFormatException; -import com.android.apksig.util.DataSink; -import com.android.apksig.util.DataSource; -import com.android.apksig.util.RunnablesExecutor; - -import java.io.Closeable; -import java.io.File; -import java.io.IOException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.SignatureException; -import java.util.List; -import java.util.Set; - -/** - * APK signing logic which is independent of how input and output APKs are stored, parsed, and - * generated. - * - *

Operating Model

- * - * The abstract operating model is that there is an input APK which is being signed, thus producing - * an output APK. In reality, there may be just an output APK being built from scratch, or the input - * APK and the output APK may be the same file. Because this engine does not deal with reading and - * writing files, it can handle all of these scenarios. - * - *

The engine is stateful and thus cannot be used for signing multiple APKs. However, once - * the engine signed an APK, the engine can be used to re-sign the APK after it has been modified. - * This may be more efficient than signing the APK using a new instance of the engine. See - * Incremental Operation. - * - *

In the engine's operating model, a signed APK is produced as follows. - *

    - *
  1. JAR entries to be signed are output,
  2. - *
  3. JAR archive is signed using JAR signing, thus adding the so-called v1 signature to the - * output,
  4. - *
  5. JAR archive is signed using APK Signature Scheme v2, thus adding the so-called v2 signature - * to the output.
  6. - *
- * - *

The input APK may contain JAR entries which, depending on the engine's configuration, may or - * may not be output (e.g., existing signatures may need to be preserved or stripped) or which the - * engine will overwrite as part of signing. The engine thus offers {@link #inputJarEntry(String)} - * which tells the client whether the input JAR entry needs to be output. This avoids the need for - * the client to hard-code the aspects of APK signing which determine which parts of input must be - * ignored. Similarly, the engine offers {@link #inputApkSigningBlock(DataSource)} to help the - * client avoid dealing with preserving or stripping APK Signature Scheme v2 signature of the input - * APK. - * - *

To use the engine to sign an input APK (or a collection of JAR entries), follow these - * steps: - *

    - *
  1. Obtain a new instance of the engine -- engine instances are stateful and thus cannot be used - * for signing multiple APKs.
  2. - *
  3. Locate the input APK's APK Signing Block and provide it to - * {@link #inputApkSigningBlock(DataSource)}.
  4. - *
  5. 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.
  6. - *
  7. For each output JAR entry, invoke {@link #outputJarEntry(String)} which may request to - * inspect the entry.
  8. - *
  9. 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.
  10. - *
  11. Locate the ZIP Central Directory and ZIP End of Central Directory sections in the output and - * invoke {@link #outputZipSections2(DataSource, DataSource, DataSource)} which may request that - * an APK Signature Block is inserted before the ZIP Central Directory. The block contains the - * output APK's APK Signature Scheme v2 signature.
  12. - *
  13. Invoke {@link #outputDone()} to signal that the APK was output in full. The engine will - * confirm that the output APK is signed.
  14. - *
  15. Invoke {@link #close()} to signal that the engine will no longer be used. This lets the - * engine free any resources it no longer needs. - *
- * - *

Some invocations of the engine may provide the client with a task to perform. The client is - * expected to perform all requested tasks before proceeding to the next stage of signing. See - * documentation of each method about the deadlines for performing the tasks requested by the - * method. - * - *

Incremental Operation

- * - * The engine supports incremental operation where a signed APK is produced, then modified and - * re-signed. This may be useful for IDEs, where an app is frequently re-signed after small changes - * by the developer. Re-signing may be more efficient than signing from scratch. - * - *

To use the engine in incremental mode, keep notifying the engine of changes to the APK through - * {@link #inputApkSigningBlock(DataSource)}, {@link #inputJarEntry(String)}, - * {@link #inputJarEntryRemoved(String)}, {@link #outputJarEntry(String)}, - * and {@link #outputJarEntryRemoved(String)}, perform the tasks requested by the engine through - * these methods, and, when a new signed APK is desired, run through steps 5 onwards to re-sign the - * APK. - * - *

Output-only Operation

- * - * The engine's abstract operating model consists of an input APK and an output APK. However, it is - * possible to use the engine in output-only mode where the engine's {@code input...} methods are - * not invoked. In this mode, the engine has less control over output because it cannot request that - * some JAR entries are not output. Nevertheless, the engine will attempt to make the output APK - * signed and will report an error if cannot do so. - * - * @see Application Signing - */ -public interface ApkSignerEngine extends Closeable { - - default void setExecutor(RunnablesExecutor executor) { - throw new UnsupportedOperationException("setExecutor method is not implemented"); - } - - /** - * Initializes the signer engine with the data already present in the apk (if any). There - * might already be data that can be reused if the entries has not been changed. - * - * @param manifestBytes - * @param entryNames - * @return set of entry names which were processed by the engine during the initialization, a - * subset of entryNames - */ - default Set initWith(byte[] manifestBytes, Set entryNames) { - throw new UnsupportedOperationException("initWith method is not implemented"); - } - - /** - * Indicates to this engine that the input APK contains the provided APK Signing Block. The - * block may contain signatures of the input APK, such as APK Signature Scheme v2 signatures. - * - * @param apkSigningBlock APK signing block of the input APK. The provided data source is - * guaranteed to not be used by the engine after this method terminates. - * - * @throws IOException if an I/O error occurs while reading the APK Signing Block - * @throws ApkFormatException if the APK Signing Block is malformed - * @throws IllegalStateException if this engine is closed - */ - void inputApkSigningBlock(DataSource apkSigningBlock) - throws IOException, ApkFormatException, IllegalStateException; - - /** - * Indicates to this engine that the specified JAR entry was encountered in the input APK. - * - *

When an input entry is updated/changed, it's OK to not invoke - * {@link #inputJarEntryRemoved(String)} before invoking this method. - * - * @return instructions about how to proceed with this entry - * - * @throws IllegalStateException if this engine is closed - */ - InputJarEntryInstructions inputJarEntry(String entryName) throws IllegalStateException; - - /** - * Indicates to this engine that the specified JAR entry was output. - * - *

It is unnecessary to invoke this method for entries added to output by this engine (e.g., - * requested by {@link #outputJarEntries()}) provided the entries were output with exactly the - * data requested by the engine. - * - *

When an already output entry is updated/changed, it's OK to not invoke - * {@link #outputJarEntryRemoved(String)} before invoking this method. - * - * @return request to inspect the entry or {@code null} if the engine does not need to inspect - * the entry. The request must be fulfilled before {@link #outputJarEntries()} is - * invoked. - * - * @throws IllegalStateException if this engine is closed - */ - InspectJarEntryRequest outputJarEntry(String entryName) throws IllegalStateException; - - /** - * Indicates to this engine that the specified JAR entry was removed from the input. It's safe - * to invoke this for entries for which {@link #inputJarEntry(String)} hasn't been invoked. - * - * @return output policy of this JAR entry. The policy indicates how this input entry affects - * the output APK. The client of this engine should use this information to determine - * how the removal of this input APK's JAR entry affects the output APK. - * - * @throws IllegalStateException if this engine is closed - */ - InputJarEntryInstructions.OutputPolicy inputJarEntryRemoved(String entryName) - throws IllegalStateException; - - /** - * Indicates to this engine that the specified JAR entry was removed from the output. It's safe - * to invoke this for entries for which {@link #outputJarEntry(String)} hasn't been invoked. - * - * @throws IllegalStateException if this engine is closed - */ - void outputJarEntryRemoved(String entryName) throws IllegalStateException; - - /** - * Indicates to this engine that all JAR entries have been output. - * - * @return request to add JAR signature to the output or {@code null} if there is no need to add - * a JAR signature. The request will contain additional JAR entries to be output. The - * request must be fulfilled before - * {@link #outputZipSections2(DataSource, DataSource, DataSource)} is invoked. - * - * @throws ApkFormatException if the APK is malformed in a way which is preventing this engine - * from producing a valid signature. For example, if the engine uses the provided - * {@code META-INF/MANIFEST.MF} as a template and the file is malformed. - * @throws NoSuchAlgorithmException if a signature could not be generated because a required - * cryptographic algorithm implementation is missing - * @throws InvalidKeyException if a signature could not be generated because a signing key is - * not suitable for generating the signature - * @throws SignatureException if an error occurred while generating a signature - * @throws IllegalStateException if there are unfulfilled requests, such as to inspect some JAR - * entries, or if the engine is closed - */ - OutputJarSignatureRequest outputJarEntries() - throws ApkFormatException, NoSuchAlgorithmException, InvalidKeyException, - SignatureException, IllegalStateException; - - /** - * Indicates to this engine that the ZIP sections comprising the output APK have been output. - * - *

The provided data sources are guaranteed to not be used by the engine after this method - * terminates. - * - * @deprecated This is now superseded by {@link #outputZipSections2(DataSource, DataSource, - * DataSource)}. - * - * @param zipEntries the section of ZIP archive containing Local File Header records and data of - * the ZIP entries. In a well-formed archive, this section starts at the start of the - * archive and extends all the way to the ZIP Central Directory. - * @param zipCentralDirectory ZIP Central Directory section - * @param zipEocd ZIP End of Central Directory (EoCD) record - * - * @return request to add an APK Signing Block to the output or {@code null} if the output must - * not contain an APK Signing Block. The request must be fulfilled before - * {@link #outputDone()} is invoked. - * - * @throws IOException if an I/O error occurs while reading the provided ZIP sections - * @throws ApkFormatException if the provided APK is malformed in a way which prevents this - * engine from producing a valid signature. For example, if the APK Signing Block - * provided to the engine is malformed. - * @throws NoSuchAlgorithmException if a signature could not be generated because a required - * cryptographic algorithm implementation is missing - * @throws InvalidKeyException if a signature could not be generated because a signing key is - * not suitable for generating the signature - * @throws SignatureException if an error occurred while generating a signature - * @throws IllegalStateException if there are unfulfilled requests, such as to inspect some JAR - * entries or to output JAR signature, or if the engine is closed - */ - @Deprecated - OutputApkSigningBlockRequest outputZipSections( - DataSource zipEntries, - DataSource zipCentralDirectory, - DataSource zipEocd) - throws IOException, ApkFormatException, NoSuchAlgorithmException, - InvalidKeyException, SignatureException, IllegalStateException; - - /** - * Indicates to this engine that the ZIP sections comprising the output APK have been output. - * - *

The provided data sources are guaranteed to not be used by the engine after this method - * terminates. - * - * @param zipEntries the section of ZIP archive containing Local File Header records and data of - * the ZIP entries. In a well-formed archive, this section starts at the start of the - * archive and extends all the way to the ZIP Central Directory. - * @param zipCentralDirectory ZIP Central Directory section - * @param zipEocd ZIP End of Central Directory (EoCD) record - * - * @return request to add an APK Signing Block to the output or {@code null} if the output must - * not contain an APK Signing Block. The request must be fulfilled before - * {@link #outputDone()} is invoked. - * - * @throws IOException if an I/O error occurs while reading the provided ZIP sections - * @throws ApkFormatException if the provided APK is malformed in a way which prevents this - * engine from producing a valid signature. For example, if the APK Signing Block - * provided to the engine is malformed. - * @throws NoSuchAlgorithmException if a signature could not be generated because a required - * cryptographic algorithm implementation is missing - * @throws InvalidKeyException if a signature could not be generated because a signing key is - * not suitable for generating the signature - * @throws SignatureException if an error occurred while generating a signature - * @throws IllegalStateException if there are unfulfilled requests, such as to inspect some JAR - * entries or to output JAR signature, or if the engine is closed - */ - OutputApkSigningBlockRequest2 outputZipSections2( - DataSource zipEntries, - DataSource zipCentralDirectory, - DataSource zipEocd) - throws IOException, ApkFormatException, NoSuchAlgorithmException, - InvalidKeyException, SignatureException, IllegalStateException; - - /** - * Indicates to this engine that the signed APK was output. - * - *

This does not change the output APK. The method helps the client confirm that the current - * output is signed. - * - * @throws IllegalStateException if there are unfulfilled requests, such as to inspect some JAR - * entries or to output signatures, or if the engine is closed - */ - void outputDone() throws IllegalStateException; - - /** - * Generates a V4 signature proto and write to output file. - * - * @param data Input data to calculate a verity hash tree and hash root - * @param outputFile To store the serialized V4 Signature. - * @param ignoreFailures Whether any failures will be silently ignored. - * @throws InvalidKeyException if a signature could not be generated because a signing key is - * not suitable for generating the signature - * @throws NoSuchAlgorithmException if a signature could not be generated because a required - * cryptographic algorithm implementation is missing - * @throws SignatureException if an error occurred while generating a signature - * @throws IOException if protobuf fails to be serialized and written to file - */ - void signV4(DataSource data, File outputFile, boolean ignoreFailures) - throws InvalidKeyException, NoSuchAlgorithmException, SignatureException, IOException; - - /** - * Checks if the signing configuration provided to the engine is capable of creating a - * SourceStamp. - */ - default boolean isEligibleForSourceStamp() { - return false; - } - - /** Generates the digest of the certificate used to sign the source stamp. */ - default byte[] generateSourceStampCertificateDigest() throws SignatureException { - return new byte[0]; - } - - /** - * Indicates to this engine that it will no longer be used. Invoking this on an already closed - * engine is OK. - * - *

This does not change the output APK. For example, if the output APK is not yet fully - * signed, it will remain so after this method terminates. - */ - @Override - void close(); - - /** - * Instructions about how to handle an input APK's JAR entry. - * - *

The instructions indicate whether to output the entry (see {@link #getOutputPolicy()}) and - * may contain a request to inspect the entry (see {@link #getInspectJarEntryRequest()}), in - * which case the request must be fulfilled before {@link ApkSignerEngine#outputJarEntries()} is - * invoked. - */ - public static class InputJarEntryInstructions { - private final OutputPolicy mOutputPolicy; - private final InspectJarEntryRequest mInspectJarEntryRequest; - - /** - * Constructs a new {@code InputJarEntryInstructions} instance with the provided entry - * output policy and without a request to inspect the entry. - */ - public InputJarEntryInstructions(OutputPolicy outputPolicy) { - this(outputPolicy, null); - } - - /** - * Constructs a new {@code InputJarEntryInstructions} instance with the provided entry - * output mode and with the provided request to inspect the entry. - * - * @param inspectJarEntryRequest request to inspect the entry or {@code null} if there's no - * need to inspect the entry. - */ - public InputJarEntryInstructions( - OutputPolicy outputPolicy, - InspectJarEntryRequest inspectJarEntryRequest) { - mOutputPolicy = outputPolicy; - mInspectJarEntryRequest = inspectJarEntryRequest; - } - - /** - * Returns the output policy for this entry. - */ - public OutputPolicy getOutputPolicy() { - return mOutputPolicy; - } - - /** - * Returns the request to inspect the JAR entry or {@code null} if there is no need to - * inspect the entry. - */ - public InspectJarEntryRequest getInspectJarEntryRequest() { - return mInspectJarEntryRequest; - } - - /** - * Output policy for an input APK's JAR entry. - */ - public static enum OutputPolicy { - /** Entry must not be output. */ - SKIP, - - /** Entry should be output. */ - OUTPUT, - - /** Entry will be output by the engine. The client can thus ignore this input entry. */ - OUTPUT_BY_ENGINE, - } - } - - /** - * Request to inspect the specified JAR entry. - * - *

The entry's uncompressed data must be provided to the data sink returned by - * {@link #getDataSink()}. Once the entry's data has been provided to the sink, {@link #done()} - * must be invoked. - */ - interface InspectJarEntryRequest { - - /** - * Returns the data sink into which the entry's uncompressed data should be sent. - */ - DataSink getDataSink(); - - /** - * Indicates that entry's data has been provided in full. - */ - void done(); - - /** - * Returns the name of the JAR entry. - */ - String getEntryName(); - } - - /** - * Request to add JAR signature (aka v1 signature) to the output APK. - * - *

Entries listed in {@link #getAdditionalJarEntries()} must be added to the output APK after - * which {@link #done()} must be invoked. - */ - interface OutputJarSignatureRequest { - - /** - * Returns JAR entries that must be added to the output APK. - */ - List getAdditionalJarEntries(); - - /** - * Indicates that the JAR entries contained in this request were added to the output APK. - */ - void done(); - - /** - * JAR entry. - */ - public static class JarEntry { - private final String mName; - private final byte[] mData; - - /** - * Constructs a new {@code JarEntry} with the provided name and data. - * - * @param data uncompressed data of the entry. Changes to this array will not be - * reflected in {@link #getData()}. - */ - public JarEntry(String name, byte[] data) { - mName = name; - mData = data.clone(); - } - - /** - * Returns the name of this ZIP entry. - */ - public String getName() { - return mName; - } - - /** - * Returns the uncompressed data of this JAR entry. - */ - public byte[] getData() { - return mData.clone(); - } - } - } - - /** - * Request to add the specified APK Signing Block to the output APK. APK Signature Scheme v2 - * signature(s) of the APK are contained in this block. - * - *

The APK Signing Block returned by {@link #getApkSigningBlock()} must be placed into the - * output APK such that the block is immediately before the ZIP Central Directory, the offset of - * ZIP Central Directory in the ZIP End of Central Directory record must be adjusted - * accordingly, and then {@link #done()} must be invoked. - * - *

If the output contains an APK Signing Block, that block must be replaced by the block - * contained in this request. - * - * @deprecated This is now superseded by {@link OutputApkSigningBlockRequest2}. - */ - @Deprecated - interface OutputApkSigningBlockRequest { - - /** - * Returns the APK Signing Block. - */ - byte[] getApkSigningBlock(); - - /** - * Indicates that the APK Signing Block was output as requested. - */ - void done(); - } - - /** - * Request to add the specified APK Signing Block to the output APK. APK Signature Scheme v2 - * signature(s) of the APK are contained in this block. - * - *

The APK Signing Block returned by {@link #getApkSigningBlock()} must be placed into the - * output APK such that the block is immediately before the ZIP Central Directory. Immediately - * before the APK Signing Block must be padding consists of the number of 0x00 bytes returned by - * {@link #getPaddingSizeBeforeApkSigningBlock()}. The offset of ZIP Central Directory in the - * ZIP End of Central Directory record must be adjusted accordingly, and then {@link #done()} - * must be invoked. - * - *

If the output contains an APK Signing Block, that block must be replaced by the block - * contained in this request. - */ - interface OutputApkSigningBlockRequest2 { - /** - * Returns the APK Signing Block. - */ - byte[] getApkSigningBlock(); - - /** - * Indicates that the APK Signing Block was output as requested. - */ - void done(); - - /** - * Returns the number of 0x00 bytes the caller must place immediately before APK Signing - * Block. - */ - int getPaddingSizeBeforeApkSigningBlock(); - } -} diff --git a/app/src/main/java/com/android/apksig/ApkVerificationIssue.java b/app/src/main/java/com/android/apksig/ApkVerificationIssue.java deleted file mode 100644 index 2aa9d0be84..0000000000 --- a/app/src/main/java/com/android/apksig/ApkVerificationIssue.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig; - -/** - * This class is intended as a lightweight representation of an APK signature verification issue - * where the client does not require the additional textual details provided by a subclass. - */ -public class ApkVerificationIssue { - /* The V2 signer(s) could not be read from the V2 signature block */ - public static final int V2_SIG_MALFORMED_SIGNERS = 1; - /* A V2 signature block exists without any V2 signers */ - public static final int V2_SIG_NO_SIGNERS = 2; - /* Failed to parse a signer's block in the V2 signature block */ - public static final int V2_SIG_MALFORMED_SIGNER = 3; - /* Failed to parse the signer's signature record in the V2 signature block */ - public static final int V2_SIG_MALFORMED_SIGNATURE = 4; - /* The V2 signer contained no signatures */ - public static final int V2_SIG_NO_SIGNATURES = 5; - /* The V2 signer's certificate could not be parsed */ - public static final int V2_SIG_MALFORMED_CERTIFICATE = 6; - /* No signing certificates exist for the V2 signer */ - public static final int V2_SIG_NO_CERTIFICATES = 7; - /* Failed to parse the V2 signer's digest record */ - public static final int V2_SIG_MALFORMED_DIGEST = 8; - /* The V3 signer(s) could not be read from the V3 signature block */ - public static final int V3_SIG_MALFORMED_SIGNERS = 9; - /* A V3 signature block exists without any V3 signers */ - public static final int V3_SIG_NO_SIGNERS = 10; - /* Failed to parse a signer's block in the V3 signature block */ - public static final int V3_SIG_MALFORMED_SIGNER = 11; - /* Failed to parse the signer's signature record in the V3 signature block */ - public static final int V3_SIG_MALFORMED_SIGNATURE = 12; - /* The V3 signer contained no signatures */ - public static final int V3_SIG_NO_SIGNATURES = 13; - /* The V3 signer's certificate could not be parsed */ - public static final int V3_SIG_MALFORMED_CERTIFICATE = 14; - /* No signing certificates exist for the V3 signer */ - public static final int V3_SIG_NO_CERTIFICATES = 15; - /* Failed to parse the V3 signer's digest record */ - public static final int V3_SIG_MALFORMED_DIGEST = 16; - /* The source stamp signer contained no signatures */ - public static final int SOURCE_STAMP_NO_SIGNATURE = 17; - /* The source stamp signer's certificate could not be parsed */ - public static final int SOURCE_STAMP_MALFORMED_CERTIFICATE = 18; - /* The source stamp contains a signature produced using an unknown algorithm */ - public static final int SOURCE_STAMP_UNKNOWN_SIG_ALGORITHM = 19; - /* Failed to parse the signer's signature in the source stamp signature block */ - public static final int SOURCE_STAMP_MALFORMED_SIGNATURE = 20; - /* The source stamp's signature block failed verification */ - public static final int SOURCE_STAMP_DID_NOT_VERIFY = 21; - /* An exception was encountered when verifying the source stamp */ - public static final int SOURCE_STAMP_VERIFY_EXCEPTION = 22; - /* The certificate digest in the APK does not match the expected digest */ - public static final int SOURCE_STAMP_EXPECTED_DIGEST_MISMATCH = 23; - /* - * The APK contains a source stamp signature block without a corresponding stamp certificate - * digest in the APK contents. - */ - public static final int SOURCE_STAMP_SIGNATURE_BLOCK_WITHOUT_CERT_DIGEST = 24; - /* - * The APK does not contain the source stamp certificate digest file nor the source stamp - * signature block. - */ - public static final int SOURCE_STAMP_CERT_DIGEST_AND_SIG_BLOCK_MISSING = 25; - /* - * None of the signatures provided by the source stamp were produced with a known signature - * algorithm. - */ - public static final int SOURCE_STAMP_NO_SUPPORTED_SIGNATURE = 26; - /* - * The source stamp signer's certificate in the signing block does not match the certificate in - * the APK. - */ - public static final int SOURCE_STAMP_CERTIFICATE_MISMATCH_BETWEEN_SIGNATURE_BLOCK_AND_APK = 27; - /* The APK could not be properly parsed due to a ZIP or APK format exception */ - public static final int MALFORMED_APK = 28; - /* An unexpected exception was caught when attempting to verify the APK's signatures */ - public static final int UNEXPECTED_EXCEPTION = 29; - /* The APK contains the certificate digest file but does not contain a stamp signature block */ - public static final int SOURCE_STAMP_SIG_MISSING = 30; - /* Source stamp block contains a malformed attribute. */ - public static final int SOURCE_STAMP_MALFORMED_ATTRIBUTE = 31; - /* Source stamp block contains an unknown attribute. */ - public static final int SOURCE_STAMP_UNKNOWN_ATTRIBUTE = 32; - /** - * Failed to parse the SigningCertificateLineage structure in the source stamp - * attributes section. - */ - public static final int SOURCE_STAMP_MALFORMED_LINEAGE = 33; - /** - * The source stamp certificate does not match the terminal node in the provided - * proof-of-rotation structure describing the stamp certificate history. - */ - public static final int SOURCE_STAMP_POR_CERT_MISMATCH = 34; - /** - * The source stamp SigningCertificateLineage attribute contains a proof-of-rotation record - * with signature(s) that did not verify. - */ - public static final int SOURCE_STAMP_POR_DID_NOT_VERIFY = 35; - /** No V1 / jar signing signature blocks were found in the APK. */ - public static final int JAR_SIG_NO_SIGNATURES = 36; - /** An exception was encountered when parsing the V1 / jar signer in the signature block. */ - public static final int JAR_SIG_PARSE_EXCEPTION = 37; - - private final int mIssueId; - private final String mFormat; - private final Object[] mParams; - - /** - * Constructs a new {@code ApkVerificationIssue} using the provided {@code format} string and - * {@code params}. - */ - public ApkVerificationIssue(String format, Object... params) { - mIssueId = -1; - mFormat = format; - mParams = params; - } - - /** - * Constructs a new {@code ApkVerificationIssue} using the provided {@code issueId} and {@code - * params}. - */ - public ApkVerificationIssue(int issueId, Object... params) { - mIssueId = issueId; - mFormat = null; - mParams = params; - } - - /** - * Returns the numeric ID for this issue. - */ - public int getIssueId() { - return mIssueId; - } - - /** - * Returns the optional parameters for this issue. - */ - public Object[] getParams() { - return mParams; - } - - @Override - public String toString() { - // If this instance was created by a subclass with a format string then return the same - // formatted String as the subclass. - if (mFormat != null) { - return String.format(mFormat, mParams); - } - StringBuilder result = new StringBuilder("mIssueId: ").append(mIssueId); - for (Object param : mParams) { - result.append(", ").append(param.toString()); - } - return result.toString(); - } -} diff --git a/app/src/main/java/com/android/apksig/ApkVerifier.java b/app/src/main/java/com/android/apksig/ApkVerifier.java deleted file mode 100644 index e6eae6b172..0000000000 --- a/app/src/main/java/com/android/apksig/ApkVerifier.java +++ /dev/null @@ -1,3329 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig; - -import static com.android.apksig.apk.ApkUtils.SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME; -import static com.android.apksig.apk.ApkUtils.computeSha256DigestBytes; -import static com.android.apksig.apk.ApkUtils.getTargetSandboxVersionFromBinaryAndroidManifest; -import static com.android.apksig.apk.ApkUtils.getTargetSdkVersionFromBinaryAndroidManifest; -import static com.android.apksig.internal.apk.ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2; -import static com.android.apksig.internal.apk.ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3; -import static com.android.apksig.internal.apk.ApkSigningBlockUtils.VERSION_JAR_SIGNATURE_SCHEME; -import static com.android.apksig.internal.apk.v1.V1SchemeConstants.MANIFEST_ENTRY_NAME; - -import com.android.apksig.apk.ApkFormatException; -import com.android.apksig.apk.ApkUtils; -import com.android.apksig.internal.apk.ApkSigResult; -import com.android.apksig.internal.apk.ApkSignerInfo; -import com.android.apksig.internal.apk.ApkSigningBlockUtils; -import com.android.apksig.internal.apk.ContentDigestAlgorithm; -import com.android.apksig.internal.apk.SignatureAlgorithm; -import com.android.apksig.internal.apk.SignatureInfo; -import com.android.apksig.internal.apk.SignatureNotFoundException; -import com.android.apksig.internal.apk.stamp.SourceStampConstants; -import com.android.apksig.internal.apk.stamp.V2SourceStampVerifier; -import com.android.apksig.internal.apk.v1.V1SchemeVerifier; -import com.android.apksig.internal.apk.v2.V2SchemeConstants; -import com.android.apksig.internal.apk.v2.V2SchemeVerifier; -import com.android.apksig.internal.apk.v3.V3SchemeConstants; -import com.android.apksig.internal.apk.v3.V3SchemeVerifier; -import com.android.apksig.internal.apk.v4.V4SchemeVerifier; -import com.android.apksig.internal.util.AndroidSdkVersion; -import com.android.apksig.internal.zip.CentralDirectoryRecord; -import com.android.apksig.internal.zip.LocalFileRecord; -import com.android.apksig.util.DataSource; -import com.android.apksig.util.DataSources; -import com.android.apksig.util.RunnablesExecutor; -import com.android.apksig.zip.ZipFormatException; - -import java.io.Closeable; -import java.io.File; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.ByteBuffer; -import java.security.NoSuchAlgorithmException; -import java.security.cert.CertificateEncodingException; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.EnumMap; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * APK signature verifier which mimics the behavior of the Android platform. - * - *

The verifier is designed to closely mimic the behavior of Android platforms. This is to enable - * the verifier to be used for checking whether an APK's signatures are expected to verify on - * Android. - * - *

Use {@link Builder} to obtain instances of this verifier. - * - * @see Application Signing - */ -public class ApkVerifier { - - private static final Map SUPPORTED_APK_SIG_SCHEME_NAMES = - loadSupportedApkSigSchemeNames(); - - private static Map loadSupportedApkSigSchemeNames() { - Map supportedMap = new HashMap<>(2); - supportedMap.put( - ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2, "APK Signature Scheme v2"); - supportedMap.put( - ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3, "APK Signature Scheme v3"); - return supportedMap; - } - - private final File mApkFile; - private final DataSource mApkDataSource; - private final File mV4SignatureFile; - - private final Integer mMinSdkVersion; - private final int mMaxSdkVersion; - - private ApkVerifier( - File apkFile, - DataSource apkDataSource, - File v4SignatureFile, - Integer minSdkVersion, - int maxSdkVersion) { - mApkFile = apkFile; - mApkDataSource = apkDataSource; - mV4SignatureFile = v4SignatureFile; - mMinSdkVersion = minSdkVersion; - mMaxSdkVersion = maxSdkVersion; - } - - /** - * Verifies the APK's signatures and returns the result of verification. The APK can be - * considered verified iff the result's {@link Result#isVerified()} returns {@code true}. - * The verification result also includes errors, warnings, and information about signers such - * as their signing certificates. - * - *

Verification succeeds iff the APK's signature is expected to verify on all Android - * platform versions specified via the {@link Builder}. If the APK's signature is expected to - * not verify on any of the specified platform versions, this method returns a result with one - * or more errors and whose {@link Result#isVerified()} returns {@code false}, or this method - * throws an exception. - * - * @throws IOException if an I/O error is encountered while reading the APK - * @throws ApkFormatException if the APK is malformed - * @throws NoSuchAlgorithmException if the APK's signatures cannot be verified because a - * required cryptographic algorithm implementation is missing - * @throws IllegalStateException if this verifier's configuration is missing required - * information. - */ - public Result verify() throws IOException, ApkFormatException, NoSuchAlgorithmException, - IllegalStateException { - Closeable in = null; - try { - DataSource apk; - if (mApkDataSource != null) { - apk = mApkDataSource; - } else if (mApkFile != null) { - RandomAccessFile f = new RandomAccessFile(mApkFile, "r"); - in = f; - apk = DataSources.asDataSource(f, 0, f.length()); - } else { - throw new IllegalStateException("APK not provided"); - } - return verify(apk); - } finally { - if (in != null) { - in.close(); - } - } - } - - /** - * Simply retrieve result that contains V1Signature only by ignoring all the verification process. - * - * @throws IOException if an I/O error is encountered while reading the APK - * @throws ApkFormatException if the APK is malformed - * @throws NoSuchAlgorithmException if the APK's signatures cannot be verified because a - * required cryptographic algorithm implementation is missing - * @throws IllegalStateException if this verifier's configuration is missing required - * information. - */ - public Result retrieveV1Signature() throws IOException, ApkFormatException, NoSuchAlgorithmException { - Closeable in = null; - try { - DataSource apk; - if (mApkDataSource != null) { - apk = mApkDataSource; - } else if (mApkFile != null) { - RandomAccessFile f = new RandomAccessFile(mApkFile, "r"); - in = f; - apk = DataSources.asDataSource(f, 0, f.length()); - } else { - throw new IllegalStateException("APK not provided"); - } - return retrieveV1Signature(apk); - } finally { - if (in != null) { - in.close(); - } - } - } - - /** - * Simply retrieve result that contains V2Signature only by ignoring all the verification process. - * - * @throws IOException if an I/O error is encountered while reading the APK - * @throws ApkFormatException if the APK is malformed - * @throws NoSuchAlgorithmException if the APK's signatures cannot be verified because a - * required cryptographic algorithm implementation is missing - * @throws IllegalStateException if this verifier's configuration is missing required - * information. - */ - public Result retrieveV2Signature() throws IOException, ApkFormatException, NoSuchAlgorithmException { - Closeable in = null; - try { - DataSource apk; - if (mApkDataSource != null) { - apk = mApkDataSource; - } else if (mApkFile != null) { - RandomAccessFile f = new RandomAccessFile(mApkFile, "r"); - in = f; - apk = DataSources.asDataSource(f, 0, f.length()); - } else { - throw new IllegalStateException("APK not provided"); - } - return retrieveV2Signature(apk); - } finally { - if (in != null) { - in.close(); - } - } - } - - /** - * Simply retrieve result that contains V2Signature only by ignoring all the verification process. - * - * @param apk APK file contents - * @throws IOException if an I/O error is encountered while reading the APK - * @throws ApkFormatException if the APK is malformed - * @throws NoSuchAlgorithmException if the APK's signatures cannot be verified because a - * required cryptographic algorithm implementation is missing - */ - private Result retrieveV2Signature(DataSource apk) throws IOException, ApkFormatException, NoSuchAlgorithmException { - int maxSdkVersion = mMaxSdkVersion; - - ApkUtils.ZipSections zipSections; - try { - zipSections = ApkUtils.findZipSections(apk); - } catch (ZipFormatException e) { - throw new ApkFormatException("Malformed APK: not a ZIP archive", e); - } - - int minSdkVersion = verifyAndGetMinSdkVersion(apk, zipSections); - - Result result = new Result(); - Map> signatureSchemeApkContentDigests = - new HashMap<>(); - - // The SUPPORTED_APK_SIG_SCHEME_NAMES contains the mapping from version number to scheme - // name, but the verifiers use this parameter as the schemes supported by the target SDK - // range. Since the code below skips signature verification based on max SDK the mapping of - // supported schemes needs to be modified to ensure the verifiers do not report a stripped - // signature for an SDK range that does not support that signature version. For instance an - // APK with V1, V2, and V3 signatures and a max SDK of O would skip the V3 signature - // verification, but the SUPPORTED_APK_SIG_SCHEME_NAMES contains version 3, so when the V2 - // verification is performed it would see the stripping protection attribute, see that V3 - // is in the list of supported signatures, and report a stripped signature. - Map supportedSchemeNames = getSupportedSchemeNames(maxSdkVersion); - - // Android N and newer attempts to verify APKs using the APK Signing Block, which can - // include v2 and/or v3 signatures. If none is found, it falls back to JAR signature - // verification. If the signature is found but does not verify, the APK is rejected. - Set foundApkSigSchemeIds = new HashSet<>(2); - if (maxSdkVersion >= AndroidSdkVersion.N) { - RunnablesExecutor executor = RunnablesExecutor.SINGLE_THREADED; - // Attempt to verify the APK using v2 signing if necessary. Platforms prior to Android P - // ignore APK Signature Scheme v3 signatures and always attempt to verify either JAR or - // APK Signature Scheme v2 signatures. Android P onwards verifies v2 signatures only if - // no APK Signature Scheme v3 (or newer scheme) signatures were found. - try { - ApkSigningBlockUtils.Result v2Result = - V2SchemeVerifier.verify( - executor, - apk, - zipSections, - supportedSchemeNames, - foundApkSigSchemeIds, - Math.max(minSdkVersion, AndroidSdkVersion.N), - maxSdkVersion); - foundApkSigSchemeIds.add(ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2); - result.mergeFrom(v2Result); - signatureSchemeApkContentDigests.put( - ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2, - getApkContentDigestsFromSigningSchemeResult(v2Result)); - } catch (ApkSigningBlockUtils.SignatureNotFoundException ignored) { - // v2 signature not required - } - if (result.containsErrors()) { - return result; - } - } - - return result; - } - - /** - * Simply retrieve result that contains V1Signature only by ignoring all the verification process. - * - * @param apk APK file contents - * @throws IOException if an I/O error is encountered while reading the APK - * @throws ApkFormatException if the APK is malformed - * @throws NoSuchAlgorithmException if the APK's signatures cannot be verified because a - * required cryptographic algorithm implementation is missing - */ - private Result retrieveV1Signature(DataSource apk) throws IOException, ApkFormatException, NoSuchAlgorithmException { - int maxSdkVersion = mMaxSdkVersion; - - ApkUtils.ZipSections zipSections; - try { - zipSections = ApkUtils.findZipSections(apk); - } catch (ZipFormatException e) { - throw new ApkFormatException("Malformed APK: not a ZIP archive", e); - } - - Result result = new Result(); - Map> signatureSchemeApkContentDigests = - new HashMap<>(); - - // The SUPPORTED_APK_SIG_SCHEME_NAMES contains the mapping from version number to scheme - // name, but the verifiers use this parameter as the schemes supported by the target SDK - // range. Since the code below skips signature verification based on max SDK the mapping of - // supported schemes needs to be modified to ensure the verifiers do not report a stripped - // signature for an SDK range that does not support that signature version. For instance an - // APK with V1, V2, and V3 signatures and a max SDK of O would skip the V3 signature - // verification, but the SUPPORTED_APK_SIG_SCHEME_NAMES contains version 3, so when the V2 - // verification is performed it would see the stripping protection attribute, see that V3 - // is in the list of supported signatures, and report a stripped signature. - Map supportedSchemeNames = getSupportedSchemeNames(maxSdkVersion); - - // Android N and newer attempts to verify APKs using the APK Signing Block, which can - // include v2 and/or v3 signatures. If none is found, it falls back to JAR signature - // verification. If the signature is found but does not verify, the APK is rejected. - Set foundApkSigSchemeIds = new HashSet<>(2); - - int minSdkVersion = verifyAndGetMinSdkVersion(apk, zipSections); - V1SchemeVerifier.Result v1Result = - V1SchemeVerifier.verify( - apk, - zipSections, - supportedSchemeNames, - foundApkSigSchemeIds, - minSdkVersion, - maxSdkVersion); - - result.mergeFrom(v1Result); - return result; - } - - - /** - * Verifies the APK's signatures and returns the result of verification. The APK can be - * considered verified iff the result's {@link Result#isVerified()} returns {@code true}. - * The verification result also includes errors, warnings, and information about signers. - * - * @param apk APK file contents - * @throws IOException if an I/O error is encountered while reading the APK - * @throws ApkFormatException if the APK is malformed - * @throws NoSuchAlgorithmException if the APK's signatures cannot be verified because a - * required cryptographic algorithm implementation is missing - */ - private Result verify(DataSource apk) - throws IOException, ApkFormatException, NoSuchAlgorithmException { - int maxSdkVersion = mMaxSdkVersion; - - ApkUtils.ZipSections zipSections; - try { - zipSections = ApkUtils.findZipSections(apk); - } catch (ZipFormatException e) { - throw new ApkFormatException("Malformed APK: not a ZIP archive", e); - } - - ByteBuffer androidManifest = null; - - int minSdkVersion = verifyAndGetMinSdkVersion(apk, zipSections); - - Result result = new Result(); - Map> signatureSchemeApkContentDigests = - new HashMap<>(); - - // The SUPPORTED_APK_SIG_SCHEME_NAMES contains the mapping from version number to scheme - // name, but the verifiers use this parameter as the schemes supported by the target SDK - // range. Since the code below skips signature verification based on max SDK the mapping of - // supported schemes needs to be modified to ensure the verifiers do not report a stripped - // signature for an SDK range that does not support that signature version. For instance an - // APK with V1, V2, and V3 signatures and a max SDK of O would skip the V3 signature - // verification, but the SUPPORTED_APK_SIG_SCHEME_NAMES contains version 3, so when the V2 - // verification is performed it would see the stripping protection attribute, see that V3 - // is in the list of supported signatures, and report a stripped signature. - Map supportedSchemeNames = getSupportedSchemeNames(maxSdkVersion); - - // Android N and newer attempts to verify APKs using the APK Signing Block, which can - // include v2 and/or v3 signatures. If none is found, it falls back to JAR signature - // verification. If the signature is found but does not verify, the APK is rejected. - Set foundApkSigSchemeIds = new HashSet<>(2); - if (maxSdkVersion >= AndroidSdkVersion.N) { - RunnablesExecutor executor = RunnablesExecutor.SINGLE_THREADED; - // Android P and newer attempts to verify APKs using APK Signature Scheme v3 - if (maxSdkVersion >= AndroidSdkVersion.P) { - try { - ApkSigningBlockUtils.Result v3Result = - V3SchemeVerifier.verify( - executor, - apk, - zipSections, - Math.max(minSdkVersion, AndroidSdkVersion.P), - maxSdkVersion); - foundApkSigSchemeIds.add(ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3); - result.mergeFrom(v3Result); - signatureSchemeApkContentDigests.put( - ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3, - getApkContentDigestsFromSigningSchemeResult(v3Result)); - } catch (ApkSigningBlockUtils.SignatureNotFoundException ignored) { - // v3 signature not required - } - if (result.containsErrors()) { - return result; - } - } - - // Attempt to verify the APK using v2 signing if necessary. Platforms prior to Android P - // ignore APK Signature Scheme v3 signatures and always attempt to verify either JAR or - // APK Signature Scheme v2 signatures. Android P onwards verifies v2 signatures only if - // no APK Signature Scheme v3 (or newer scheme) signatures were found. - if (minSdkVersion < AndroidSdkVersion.P || foundApkSigSchemeIds.isEmpty()) { - try { - ApkSigningBlockUtils.Result v2Result = - V2SchemeVerifier.verify( - executor, - apk, - zipSections, - supportedSchemeNames, - foundApkSigSchemeIds, - Math.max(minSdkVersion, AndroidSdkVersion.N), - maxSdkVersion); - foundApkSigSchemeIds.add(ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2); - result.mergeFrom(v2Result); - signatureSchemeApkContentDigests.put( - ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2, - getApkContentDigestsFromSigningSchemeResult(v2Result)); - } catch (ApkSigningBlockUtils.SignatureNotFoundException ignored) { - // v2 signature not required - } - if (result.containsErrors()) { - return result; - } - } - - // If v4 file is specified, use additional verification on it - if (mV4SignatureFile != null) { - final ApkSigningBlockUtils.Result v4Result = - V4SchemeVerifier.verify(apk, mV4SignatureFile); - foundApkSigSchemeIds.add( - ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V4); - result.mergeFrom(v4Result); - if (result.containsErrors()) { - return result; - } - } - } - - // Android O and newer requires that APKs targeting security sandbox version 2 and higher - // are signed using APK Signature Scheme v2 or newer. - if (maxSdkVersion >= AndroidSdkVersion.O) { - if (androidManifest == null) { - androidManifest = getAndroidManifestFromApk(apk, zipSections); - } - int targetSandboxVersion = - getTargetSandboxVersionFromBinaryAndroidManifest(androidManifest.slice()); - if (targetSandboxVersion > 1) { - if (foundApkSigSchemeIds.isEmpty()) { - result.addError( - Issue.NO_SIG_FOR_TARGET_SANDBOX_VERSION, - targetSandboxVersion); - } - } - } - - List cdRecords = - V1SchemeVerifier.parseZipCentralDirectory(apk, zipSections); - - // Attempt to verify the APK using JAR signing if necessary. Platforms prior to Android N - // ignore APK Signature Scheme v2 signatures and always attempt to verify JAR signatures. - // Android N onwards verifies JAR signatures only if no APK Signature Scheme v2 (or newer - // scheme) signatures were found. - if ((minSdkVersion < AndroidSdkVersion.N) || (foundApkSigSchemeIds.isEmpty())) { - V1SchemeVerifier.Result v1Result = - V1SchemeVerifier.verify( - apk, - zipSections, - supportedSchemeNames, - foundApkSigSchemeIds, - minSdkVersion, - maxSdkVersion); - result.mergeFrom(v1Result); - signatureSchemeApkContentDigests.put( - ApkSigningBlockUtils.VERSION_JAR_SIGNATURE_SCHEME, - getApkContentDigestFromV1SigningScheme(cdRecords, apk, zipSections)); - } - if (result.containsErrors()) { - return result; - } - - // Verify the SourceStamp, if found in the APK. - try { - CentralDirectoryRecord sourceStampCdRecord = null; - for (CentralDirectoryRecord cdRecord : cdRecords) { - if (SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME.equals( - cdRecord.getName())) { - sourceStampCdRecord = cdRecord; - break; - } - } - // If SourceStamp file is found inside the APK, there must be a SourceStamp - // block in the APK signing block as well. - if (sourceStampCdRecord != null) { - byte[] sourceStampCertificateDigest = - LocalFileRecord.getUncompressedData( - apk, - sourceStampCdRecord, - zipSections.getZipCentralDirectoryOffset()); - ApkSigResult sourceStampResult = - V2SourceStampVerifier.verify( - apk, - zipSections, - sourceStampCertificateDigest, - signatureSchemeApkContentDigests, - Math.max(minSdkVersion, AndroidSdkVersion.R), - maxSdkVersion); - result.mergeFrom(sourceStampResult); - } - } catch (SignatureNotFoundException ignored) { - result.addWarning(Issue.SOURCE_STAMP_SIG_MISSING); - } catch (ZipFormatException e) { - throw new ApkFormatException("Failed to read APK", e); - } - if (result.containsErrors()) { - return result; - } - - // Check whether v1 and v2 scheme signer identifies match, provided both v1 and v2 - // signatures verified. - if ((result.isVerifiedUsingV1Scheme()) && (result.isVerifiedUsingV2Scheme())) { - ArrayList v1Signers = - new ArrayList<>(result.getV1SchemeSigners()); - ArrayList v2Signers = - new ArrayList<>(result.getV2SchemeSigners()); - ArrayList v1SignerCerts = new ArrayList<>(); - ArrayList v2SignerCerts = new ArrayList<>(); - for (Result.V1SchemeSignerInfo signer : v1Signers) { - try { - v1SignerCerts.add(new ByteArray(signer.getCertificate().getEncoded())); - } catch (CertificateEncodingException e) { - throw new IllegalStateException( - "Failed to encode JAR signer " + signer.getName() + " certs", e); - } - } - for (Result.V2SchemeSignerInfo signer : v2Signers) { - try { - v2SignerCerts.add(new ByteArray(signer.getCertificate().getEncoded())); - } catch (CertificateEncodingException e) { - throw new IllegalStateException( - "Failed to encode APK Signature Scheme v2 signer (index: " - + signer.getIndex() + ") certs", - e); - } - } - - for (int i = 0; i < v1SignerCerts.size(); i++) { - ByteArray v1Cert = v1SignerCerts.get(i); - if (!v2SignerCerts.contains(v1Cert)) { - Result.V1SchemeSignerInfo v1Signer = v1Signers.get(i); - v1Signer.addError(Issue.V2_SIG_MISSING); - break; - } - } - for (int i = 0; i < v2SignerCerts.size(); i++) { - ByteArray v2Cert = v2SignerCerts.get(i); - if (!v1SignerCerts.contains(v2Cert)) { - Result.V2SchemeSignerInfo v2Signer = v2Signers.get(i); - v2Signer.addError(Issue.JAR_SIG_MISSING); - break; - } - } - } - - // If there is a v3 scheme signer and an earlier scheme signer, make sure that there is a - // match, or in the event of signing certificate rotation, that the v1/v2 scheme signer - // matches the oldest signing certificate in the provided SigningCertificateLineage - if (result.isVerifiedUsingV3Scheme() - && (result.isVerifiedUsingV1Scheme() || result.isVerifiedUsingV2Scheme())) { - SigningCertificateLineage lineage = result.getSigningCertificateLineage(); - X509Certificate oldSignerCert; - if (result.isVerifiedUsingV1Scheme()) { - List v1Signers = result.getV1SchemeSigners(); - if (v1Signers.size() != 1) { - // APK Signature Scheme v3 only supports single-signers, error to sign with - // multiple and then only one - result.addError(Issue.V3_SIG_MULTIPLE_PAST_SIGNERS); - } - oldSignerCert = v1Signers.get(0).mCertChain.get(0); - } else { - List v2Signers = result.getV2SchemeSigners(); - if (v2Signers.size() != 1) { - // APK Signature Scheme v3 only supports single-signers, error to sign with - // multiple and then only one - result.addError(Issue.V3_SIG_MULTIPLE_PAST_SIGNERS); - } - oldSignerCert = v2Signers.get(0).mCerts.get(0); - } - if (lineage == null) { - // no signing certificate history with which to contend, just make sure that v3 - // matches previous versions - List v3Signers = result.getV3SchemeSigners(); - if (v3Signers.size() != 1) { - // multiple v3 signers should never exist without rotation history, since - // multiple signers implies a different signer for different platform versions - result.addError(Issue.V3_SIG_MULTIPLE_SIGNERS); - } - try { - if (!Arrays.equals(oldSignerCert.getEncoded(), - v3Signers.get(0).mCerts.get(0).getEncoded())) { - result.addError(Issue.V3_SIG_PAST_SIGNERS_MISMATCH); - } - } catch (CertificateEncodingException e) { - // we just go the encoding for the v1/v2 certs above, so must be v3 - throw new RuntimeException( - "Failed to encode APK Signature Scheme v3 signer cert", e); - } - } else { - // we have some signing history, make sure that the root of the history is the same - // as our v1/v2 signer - try { - lineage = lineage.getSubLineage(oldSignerCert); - if (lineage.size() != 1) { - // the v1/v2 signer was found, but not at the root of the lineage - result.addError(Issue.V3_SIG_PAST_SIGNERS_MISMATCH); - } - } catch (IllegalArgumentException e) { - // the v1/v2 signer was not found in the lineage - result.addError(Issue.V3_SIG_PAST_SIGNERS_MISMATCH); - } - } - } - - - // If there is a v4 scheme signer, make sure that their certificates match. - // The apkDigest field in the v4 signature should match the selected v2/v3. - if (result.isVerifiedUsingV4Scheme()) { - List v4Signers = result.getV4SchemeSigners(); - if (v4Signers.size() != 1) { - result.addError(Issue.V4_SIG_MULTIPLE_SIGNERS); - } - - List digestsFromV4 = - v4Signers.get(0).getContentDigests(); - if (digestsFromV4.size() != 1) { - result.addError(Issue.V4_SIG_V2_V3_DIGESTS_MISMATCH); - } - final byte[] digestFromV4 = digestsFromV4.get(0).getValue(); - - if (result.isVerifiedUsingV3Scheme()) { - List v3Signers = result.getV3SchemeSigners(); - if (v3Signers.size() != 1) { - result.addError(Issue.V4_SIG_MULTIPLE_SIGNERS); - } - - // Compare certificates. - checkV4Certificate(v4Signers.get(0).mCerts, v3Signers.get(0).mCerts, result); - - // Compare digests. - final byte[] digestFromV3 = pickBestDigestForV4( - v3Signers.get(0).getContentDigests()); - if (!Arrays.equals(digestFromV4, digestFromV3)) { - result.addError(Issue.V4_SIG_V2_V3_DIGESTS_MISMATCH); - } - } else if (result.isVerifiedUsingV2Scheme()) { - List v2Signers = result.getV2SchemeSigners(); - if (v2Signers.size() != 1) { - result.addError(Issue.V4_SIG_MULTIPLE_SIGNERS); - } - - // Compare certificates. - checkV4Certificate(v4Signers.get(0).mCerts, v2Signers.get(0).mCerts, result); - - // Compare digests. - final byte[] digestFromV2 = pickBestDigestForV4( - v2Signers.get(0).getContentDigests()); - if (!Arrays.equals(digestFromV4, digestFromV2)) { - result.addError(Issue.V4_SIG_V2_V3_DIGESTS_MISMATCH); - } - } else { - throw new RuntimeException("V4 signature must be also verified with V2/V3"); - } - } - - // If the targetSdkVersion has a minimum required signature scheme version then verify - // that the APK was signed with at least that version. - try { - if (androidManifest == null) { - androidManifest = getAndroidManifestFromApk(apk, zipSections); - } - } catch (ApkFormatException e) { - // If the manifest is not available then skip the minimum signature scheme requirement - // to support bundle verification. - } - if (androidManifest != null) { - int targetSdkVersion = getTargetSdkVersionFromBinaryAndroidManifest( - androidManifest.slice()); - int minSchemeVersion = getMinimumSignatureSchemeVersionForTargetSdk(targetSdkVersion); - // The platform currently only enforces a single minimum signature scheme version, but - // when later platform versions support another minimum version this will need to be - // expanded to verify the minimum based on the target and maximum SDK version. - if (minSchemeVersion > VERSION_JAR_SIGNATURE_SCHEME - && maxSdkVersion >= targetSdkVersion) { - switch (minSchemeVersion) { - case VERSION_APK_SIGNATURE_SCHEME_V2: - if (result.isVerifiedUsingV2Scheme()) { - break; - } - // Allow this case to fall through to the next as a signature satisfying a - // later scheme version will also satisfy this requirement. - case VERSION_APK_SIGNATURE_SCHEME_V3: - if (result.isVerifiedUsingV3Scheme()) { - break; - } - result.addError(Issue.MIN_SIG_SCHEME_FOR_TARGET_SDK_NOT_MET, - targetSdkVersion, - minSchemeVersion); - } - } - } - - if (result.containsErrors()) { - return result; - } - - // Verified - result.setVerified(); - if (result.isVerifiedUsingV3Scheme()) { - List v3Signers = result.getV3SchemeSigners(); - result.addSignerCertificate(v3Signers.get(v3Signers.size() - 1).getCertificate()); - } else if (result.isVerifiedUsingV2Scheme()) { - for (Result.V2SchemeSignerInfo signerInfo : result.getV2SchemeSigners()) { - result.addSignerCertificate(signerInfo.getCertificate()); - } - } else if (result.isVerifiedUsingV1Scheme()) { - for (Result.V1SchemeSignerInfo signerInfo : result.getV1SchemeSigners()) { - result.addSignerCertificate(signerInfo.getCertificate()); - } - } else { - throw new RuntimeException( - "APK verified, but has not verified using any of v1, v2 or v3 schemes"); - } - - return result; - } - - /** - * Verifies and returns the minimum SDK version, either as provided to the builder or as read - * from the {@code apk}'s AndroidManifest.xml. - */ - private int verifyAndGetMinSdkVersion(DataSource apk, ApkUtils.ZipSections zipSections) - throws ApkFormatException, IOException { - if (mMinSdkVersion != null) { - if (mMinSdkVersion < 0) { - throw new IllegalArgumentException( - "minSdkVersion must not be negative: " + mMinSdkVersion); - } - if ((mMinSdkVersion != null) && (mMinSdkVersion > mMaxSdkVersion)) { - throw new IllegalArgumentException( - "minSdkVersion (" + mMinSdkVersion + ") > maxSdkVersion (" + mMaxSdkVersion - + ")"); - } - return mMinSdkVersion; - } - - ByteBuffer androidManifest = null; - // Need to obtain minSdkVersion from the APK's AndroidManifest.xml - if (androidManifest == null) { - androidManifest = getAndroidManifestFromApk(apk, zipSections); - } - int minSdkVersion = - ApkUtils.getMinSdkVersionFromBinaryAndroidManifest(androidManifest.slice()); - if (minSdkVersion > mMaxSdkVersion) { - throw new IllegalArgumentException( - "minSdkVersion from APK (" + minSdkVersion + ") > maxSdkVersion (" - + mMaxSdkVersion + ")"); - } - return minSdkVersion; - } - - /** - * Returns the mapping of signature scheme version to signature scheme name for all signature - * schemes starting from V2 supported by the {@code maxSdkVersion}. - */ - private static Map getSupportedSchemeNames(int maxSdkVersion) { - Map supportedSchemeNames; - if (maxSdkVersion >= AndroidSdkVersion.P) { - supportedSchemeNames = SUPPORTED_APK_SIG_SCHEME_NAMES; - } else if (maxSdkVersion >= AndroidSdkVersion.N) { - supportedSchemeNames = new HashMap<>(1); - supportedSchemeNames.put(ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2, - SUPPORTED_APK_SIG_SCHEME_NAMES.get( - ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2)); - } else { - supportedSchemeNames = Collections.emptyMap(); - } - return supportedSchemeNames; - } - - /** - * Verifies the APK's source stamp signature and returns the result of the verification. - * - *

The APK's source stamp can be considered verified if the result's {@link - * Result#isVerified} returns {@code true}. The details of the source stamp verification can - * be obtained from the result's {@link Result#getSourceStampInfo()}} including the success or - * failure cause from {@link Result.SourceStampInfo#getSourceStampVerificationStatus()}. If the - * verification fails additional details regarding the failure can be obtained from {@link - * Result#getAllErrors()}}. - */ - public Result verifySourceStamp() { - return verifySourceStamp(null); - } - - /** - * Verifies the APK's source stamp signature, including verification that the SHA-256 digest of - * the stamp signing certificate matches the {@code expectedCertDigest}, and returns the result - * of the verification. - * - *

A value of {@code null} for the {@code expectedCertDigest} will verify the source stamp, - * if present, without verifying the actual source stamp certificate used to sign the source - * stamp. This can be used to verify an APK contains a properly signed source stamp without - * verifying a particular signer. - * - * @see #verifySourceStamp() - */ - public Result verifySourceStamp(String expectedCertDigest) { - Closeable in = null; - try { - DataSource apk; - if (mApkDataSource != null) { - apk = mApkDataSource; - } else if (mApkFile != null) { - RandomAccessFile f = new RandomAccessFile(mApkFile, "r"); - in = f; - apk = DataSources.asDataSource(f, 0, f.length()); - } else { - throw new IllegalStateException("APK not provided"); - } - return verifySourceStamp(apk, expectedCertDigest); - } catch (IOException e) { - return createSourceStampResultWithError( - Result.SourceStampInfo.SourceStampVerificationStatus.VERIFICATION_ERROR, - Issue.UNEXPECTED_EXCEPTION, e); - } finally { - if (in != null) { - try { - in.close(); - } catch (IOException ignored) { - } - } - } - } - - /** - * Verifies the provided {@code apk}'s source stamp signature, including verification of the - * SHA-256 digest of the stamp signing certificate matches the {@code expectedCertDigest}, and - * returns the result of the verification. - * - * @see #verifySourceStamp(String) - */ - private Result verifySourceStamp(DataSource apk, String expectedCertDigest) { - try { - ApkUtils.ZipSections zipSections = ApkUtils.findZipSections(apk); - int minSdkVersion = verifyAndGetMinSdkVersion(apk, zipSections); - - // Attempt to obtain the source stamp's certificate digest from the APK. - List cdRecords = - V1SchemeVerifier.parseZipCentralDirectory(apk, zipSections); - CentralDirectoryRecord sourceStampCdRecord = null; - for (CentralDirectoryRecord cdRecord : cdRecords) { - if (SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME.equals(cdRecord.getName())) { - sourceStampCdRecord = cdRecord; - break; - } - } - - // If the source stamp's certificate digest is not available within the APK then the - // source stamp cannot be verified; check if a source stamp signing block is in the - // APK's signature block to determine the appropriate status to return. - if (sourceStampCdRecord == null) { - boolean stampSigningBlockFound; - try { - ApkSigningBlockUtils.Result result = new ApkSigningBlockUtils.Result( - ApkSigningBlockUtils.VERSION_SOURCE_STAMP); - ApkSigningBlockUtils.findSignature(apk, zipSections, - SourceStampConstants.V2_SOURCE_STAMP_BLOCK_ID, result); - stampSigningBlockFound = true; - } catch (ApkSigningBlockUtils.SignatureNotFoundException e) { - stampSigningBlockFound = false; - } - if (stampSigningBlockFound) { - return createSourceStampResultWithError( - Result.SourceStampInfo.SourceStampVerificationStatus.STAMP_NOT_VERIFIED, - Issue.SOURCE_STAMP_SIGNATURE_BLOCK_WITHOUT_CERT_DIGEST); - } else { - return createSourceStampResultWithError( - Result.SourceStampInfo.SourceStampVerificationStatus.STAMP_MISSING, - Issue.SOURCE_STAMP_CERT_DIGEST_AND_SIG_BLOCK_MISSING); - } - } - - // Verify that the contents of the source stamp certificate digest match the expected - // value, if provided. - byte[] sourceStampCertificateDigest = - LocalFileRecord.getUncompressedData( - apk, - sourceStampCdRecord, - zipSections.getZipCentralDirectoryOffset()); - if (expectedCertDigest != null) { - String actualCertDigest = ApkSigningBlockUtils.toHex(sourceStampCertificateDigest); - if (!expectedCertDigest.equalsIgnoreCase(actualCertDigest)) { - return createSourceStampResultWithError( - Result.SourceStampInfo.SourceStampVerificationStatus - .CERT_DIGEST_MISMATCH, - Issue.SOURCE_STAMP_EXPECTED_DIGEST_MISMATCH, actualCertDigest, - expectedCertDigest); - } - } - - Map> signatureSchemeApkContentDigests = - new HashMap<>(); - Map supportedSchemeNames = getSupportedSchemeNames(mMaxSdkVersion); - Set foundApkSigSchemeIds = new HashSet<>(2); - - Result result = new Result(); - ApkSigningBlockUtils.Result v3Result = null; - if (mMaxSdkVersion >= AndroidSdkVersion.P) { - v3Result = getApkContentDigests(apk, zipSections, foundApkSigSchemeIds, - supportedSchemeNames, signatureSchemeApkContentDigests, - VERSION_APK_SIGNATURE_SCHEME_V3, - Math.max(minSdkVersion, AndroidSdkVersion.P)); - if (v3Result != null && v3Result.containsErrors()) { - result.mergeFrom(v3Result); - return mergeSourceStampResult( - Result.SourceStampInfo.SourceStampVerificationStatus.VERIFICATION_ERROR, - result); - } - } - - ApkSigningBlockUtils.Result v2Result = null; - if (mMaxSdkVersion >= AndroidSdkVersion.N && (minSdkVersion < AndroidSdkVersion.P - || foundApkSigSchemeIds.isEmpty())) { - v2Result = getApkContentDigests(apk, zipSections, foundApkSigSchemeIds, - supportedSchemeNames, signatureSchemeApkContentDigests, - VERSION_APK_SIGNATURE_SCHEME_V2, - Math.max(minSdkVersion, AndroidSdkVersion.N)); - if (v2Result != null && v2Result.containsErrors()) { - result.mergeFrom(v2Result); - return mergeSourceStampResult( - Result.SourceStampInfo.SourceStampVerificationStatus.VERIFICATION_ERROR, - result); - } - } - - if (minSdkVersion < AndroidSdkVersion.N || foundApkSigSchemeIds.isEmpty()) { - signatureSchemeApkContentDigests.put(VERSION_JAR_SIGNATURE_SCHEME, - getApkContentDigestFromV1SigningScheme(cdRecords, apk, zipSections)); - } - - ApkSigResult sourceStampResult = - V2SourceStampVerifier.verify( - apk, - zipSections, - sourceStampCertificateDigest, - signatureSchemeApkContentDigests, - minSdkVersion, - mMaxSdkVersion); - result.mergeFrom(sourceStampResult); - // Since the caller is only seeking to verify the source stamp the Result can be marked - // as verified if the source stamp verification was successful. - if (sourceStampResult.verified) { - result.setVerified(); - } else { - // To prevent APK signature verification with a failed / missing source stamp the - // source stamp verification will only log warnings; to allow the caller to capture - // the failure reason treat all warnings as errors. - result.setWarningsAsErrors(true); - } - return result; - } catch (ApkFormatException | IOException | ZipFormatException e) { - return createSourceStampResultWithError( - Result.SourceStampInfo.SourceStampVerificationStatus.VERIFICATION_ERROR, - Issue.MALFORMED_APK, e); - } catch (NoSuchAlgorithmException e) { - return createSourceStampResultWithError( - Result.SourceStampInfo.SourceStampVerificationStatus.VERIFICATION_ERROR, - Issue.UNEXPECTED_EXCEPTION, e); - } catch (SignatureNotFoundException e) { - return createSourceStampResultWithError( - Result.SourceStampInfo.SourceStampVerificationStatus.STAMP_NOT_VERIFIED, - Issue.SOURCE_STAMP_SIG_MISSING); - } - } - - /** - * Creates and returns a {@code Result} that can be returned for source stamp verification - * with the provided source stamp {@code verificationStatus}, and logs an error for the - * specified {@code issue} and {@code params}. - */ - private static Result createSourceStampResultWithError( - Result.SourceStampInfo.SourceStampVerificationStatus verificationStatus, Issue issue, - Object... params) { - Result result = new Result(); - result.addError(issue, params); - return mergeSourceStampResult(verificationStatus, result); - } - - /** - * Creates a new {@link Result.SourceStampInfo} under the provided {@code result} and sets the - * source stamp status to the provided {@code verificationStatus}. - */ - private static Result mergeSourceStampResult( - Result.SourceStampInfo.SourceStampVerificationStatus verificationStatus, - Result result) { - result.mSourceStampInfo = new Result.SourceStampInfo(verificationStatus); - return result; - } - - /** - * Obtains the APK content digest(s) and adds them to the provided {@code - * sigSchemeApkContentDigests}, returning an {@code ApkSigningBlockUtils.Result} that can be - * merged with a {@code Result} to notify the client of any errors. - * - *

Note, this method currently only supports signature scheme V2 and V3; to obtain the - * content digests for V1 signatures use {@link - * #getApkContentDigestFromV1SigningScheme(List, DataSource, ApkUtils.ZipSections)}. If a - * signature scheme version other than V2 or V3 is provided a {@code null} value will be - * returned. - */ - private ApkSigningBlockUtils.Result getApkContentDigests(DataSource apk, - ApkUtils.ZipSections zipSections, Set foundApkSigSchemeIds, - Map supportedSchemeNames, - Map> sigSchemeApkContentDigests, - int apkSigSchemeVersion, int minSdkVersion) - throws IOException, NoSuchAlgorithmException { - if (!(apkSigSchemeVersion == VERSION_APK_SIGNATURE_SCHEME_V2 - || apkSigSchemeVersion == VERSION_APK_SIGNATURE_SCHEME_V3)) { - return null; - } - ApkSigningBlockUtils.Result result = new ApkSigningBlockUtils.Result(apkSigSchemeVersion); - SignatureInfo signatureInfo; - try { - int sigSchemeBlockId = apkSigSchemeVersion == VERSION_APK_SIGNATURE_SCHEME_V3 - ? V3SchemeConstants.APK_SIGNATURE_SCHEME_V3_BLOCK_ID - : V2SchemeConstants.APK_SIGNATURE_SCHEME_V2_BLOCK_ID; - signatureInfo = ApkSigningBlockUtils.findSignature(apk, zipSections, - sigSchemeBlockId, result); - } catch (ApkSigningBlockUtils.SignatureNotFoundException e) { - return null; - } - foundApkSigSchemeIds.add(apkSigSchemeVersion); - - Set contentDigestsToVerify = new HashSet<>(1); - if (apkSigSchemeVersion == VERSION_APK_SIGNATURE_SCHEME_V2) { - V2SchemeVerifier.parseSigners(signatureInfo.signatureBlock, - contentDigestsToVerify, supportedSchemeNames, - foundApkSigSchemeIds, minSdkVersion, mMaxSdkVersion, result); - } else { - V3SchemeVerifier.parseSigners(signatureInfo.signatureBlock, - contentDigestsToVerify, result); - } - Map apkContentDigests = new EnumMap<>( - ContentDigestAlgorithm.class); - for (ApkSigningBlockUtils.Result.SignerInfo signerInfo : result.signers) { - for (ApkSigningBlockUtils.Result.SignerInfo.ContentDigest contentDigest : - signerInfo.contentDigests) { - SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.findById( - contentDigest.getSignatureAlgorithmId()); - if (signatureAlgorithm == null) { - continue; - } - apkContentDigests.put(signatureAlgorithm.getContentDigestAlgorithm(), - contentDigest.getValue()); - } - } - sigSchemeApkContentDigests.put(apkSigSchemeVersion, apkContentDigests); - return result; - } - - private static void checkV4Certificate(List v4Certs, - List v2v3Certs, Result result) { - try { - byte[] v4Cert = v4Certs.get(0).getEncoded(); - byte[] cert = v2v3Certs.get(0).getEncoded(); - if (!Arrays.equals(cert, v4Cert)) { - result.addError(Issue.V4_SIG_V2_V3_SIGNERS_MISMATCH); - } - } catch (CertificateEncodingException e) { - throw new RuntimeException("Failed to encode APK signer cert", e); - } - } - - private static byte[] pickBestDigestForV4( - List contentDigests) { - Map apkContentDigests = new HashMap<>(); - collectApkContentDigests(contentDigests, apkContentDigests); - return ApkSigningBlockUtils.pickBestDigestForV4(apkContentDigests); - } - - private static Map getApkContentDigestsFromSigningSchemeResult( - ApkSigningBlockUtils.Result apkSigningSchemeResult) { - Map apkContentDigests = new HashMap<>(); - for (ApkSigningBlockUtils.Result.SignerInfo signerInfo : apkSigningSchemeResult.signers) { - collectApkContentDigests(signerInfo.contentDigests, apkContentDigests); - } - return apkContentDigests; - } - - private static Map getApkContentDigestFromV1SigningScheme( - List cdRecords, - DataSource apk, - ApkUtils.ZipSections zipSections) - throws IOException, ApkFormatException { - CentralDirectoryRecord manifestCdRecord = null; - Map v1ContentDigest = new EnumMap<>( - ContentDigestAlgorithm.class); - for (CentralDirectoryRecord cdRecord : cdRecords) { - if (MANIFEST_ENTRY_NAME.equals(cdRecord.getName())) { - manifestCdRecord = cdRecord; - break; - } - } - if (manifestCdRecord == null) { - // No JAR signing manifest file found. For SourceStamp verification, returning an empty - // digest is enough since this would affect the final digest signed by the stamp, and - // thus an empty digest will invalidate that signature. - return v1ContentDigest; - } - try { - byte[] manifestBytes = - LocalFileRecord.getUncompressedData( - apk, manifestCdRecord, zipSections.getZipCentralDirectoryOffset()); - v1ContentDigest.put( - ContentDigestAlgorithm.SHA256, computeSha256DigestBytes(manifestBytes)); - return v1ContentDigest; - } catch (ZipFormatException e) { - throw new ApkFormatException("Failed to read APK", e); - } - } - - private static void collectApkContentDigests( - List contentDigests, - Map apkContentDigests) { - for (ApkSigningBlockUtils.Result.SignerInfo.ContentDigest contentDigest : contentDigests) { - SignatureAlgorithm signatureAlgorithm = - SignatureAlgorithm.findById(contentDigest.getSignatureAlgorithmId()); - if (signatureAlgorithm == null) { - continue; - } - ContentDigestAlgorithm contentDigestAlgorithm = - signatureAlgorithm.getContentDigestAlgorithm(); - apkContentDigests.put(contentDigestAlgorithm, contentDigest.getValue()); - } - - } - - private static ByteBuffer getAndroidManifestFromApk( - DataSource apk, ApkUtils.ZipSections zipSections) - throws IOException, ApkFormatException { - List cdRecords = - V1SchemeVerifier.parseZipCentralDirectory(apk, zipSections); - try { - return ApkSigner.getAndroidManifestFromApk( - cdRecords, - apk.slice(0, zipSections.getZipCentralDirectoryOffset())); - } catch (ZipFormatException e) { - throw new ApkFormatException("Failed to read AndroidManifest.xml", e); - } - } - - private static int getMinimumSignatureSchemeVersionForTargetSdk(int targetSdkVersion) { - if (targetSdkVersion >= AndroidSdkVersion.R) { - return VERSION_APK_SIGNATURE_SCHEME_V2; - } - return VERSION_JAR_SIGNATURE_SCHEME; - } - - /** - * Result of verifying an APKs signatures. The APK can be considered verified iff - * {@link #isVerified()} returns {@code true}. - */ - public static class Result { - private final List mErrors = new ArrayList<>(); - private final List mWarnings = new ArrayList<>(); - private final List mSignerCerts = new ArrayList<>(); - private final List mV1SchemeSigners = new ArrayList<>(); - private final List mV1SchemeIgnoredSigners = new ArrayList<>(); - private final List mV2SchemeSigners = new ArrayList<>(); - private final List mV3SchemeSigners = new ArrayList<>(); - private final List mV4SchemeSigners = new ArrayList<>(); - private SourceStampInfo mSourceStampInfo; - - private boolean mVerified; - private boolean mVerifiedUsingV1Scheme; - private boolean mVerifiedUsingV2Scheme; - private boolean mVerifiedUsingV3Scheme; - private boolean mVerifiedUsingV4Scheme; - private boolean mSourceStampVerified; - private boolean mWarningsAsErrors; - private SigningCertificateLineage mSigningCertificateLineage; - - /** - * Returns {@code true} if the APK's signatures verified. - */ - public boolean isVerified() { - return mVerified; - } - - private void setVerified() { - mVerified = true; - } - - /** - * Returns {@code true} if the APK's JAR signatures verified. - */ - public boolean isVerifiedUsingV1Scheme() { - return mVerifiedUsingV1Scheme; - } - - /** - * Returns {@code true} if the APK's APK Signature Scheme v2 signatures verified. - */ - public boolean isVerifiedUsingV2Scheme() { - return mVerifiedUsingV2Scheme; - } - - /** - * Returns {@code true} if the APK's APK Signature Scheme v3 signature verified. - */ - public boolean isVerifiedUsingV3Scheme() { - return mVerifiedUsingV3Scheme; - } - - /** - * Returns {@code true} if the APK's APK Signature Scheme v4 signature verified. - */ - public boolean isVerifiedUsingV4Scheme() { - return mVerifiedUsingV4Scheme; - } - - /** - * Returns {@code true} if the APK's SourceStamp signature verified. - */ - public boolean isSourceStampVerified() { - return mSourceStampVerified; - } - - /** - * Returns the verified signers' certificates, one per signer. - */ - public List getSignerCertificates() { - return mSignerCerts; - } - - private void addSignerCertificate(X509Certificate cert) { - mSignerCerts.add(cert); - } - - /** - * Returns information about JAR signers associated with the APK's signature. These are the - * signers used by Android. - * - * @see #getV1SchemeIgnoredSigners() - */ - public List getV1SchemeSigners() { - return mV1SchemeSigners; - } - - /** - * Returns information about JAR signers ignored by the APK's signature verification - * process. These signers are ignored by Android. However, each signer's errors or warnings - * will contain information about why they are ignored. - * - * @see #getV1SchemeSigners() - */ - public List getV1SchemeIgnoredSigners() { - return mV1SchemeIgnoredSigners; - } - - /** - * Returns information about APK Signature Scheme v2 signers associated with the APK's - * signature. - */ - public List getV2SchemeSigners() { - return mV2SchemeSigners; - } - - /** - * Returns information about APK Signature Scheme v3 signers associated with the APK's - * signature. - * - * Multiple signers represent different targeted platform versions, not - * a signing identity of multiple signers. APK Signature Scheme v3 only supports single - * signer identities. - */ - public List getV3SchemeSigners() { - return mV3SchemeSigners; - } - - private List getV4SchemeSigners() { - return mV4SchemeSigners; - } - - /** - * Returns information about SourceStamp associated with the APK's signature. - */ - public SourceStampInfo getSourceStampInfo() { - return mSourceStampInfo; - } - - /** - * Returns the combined SigningCertificateLineage associated with this APK's APK Signature - * Scheme v3 signing block. - */ - public SigningCertificateLineage getSigningCertificateLineage() { - return mSigningCertificateLineage; - } - - void addError(Issue msg, Object... parameters) { - mErrors.add(new IssueWithParams(msg, parameters)); - } - - void addWarning(Issue msg, Object... parameters) { - mWarnings.add(new IssueWithParams(msg, parameters)); - } - - /** - * Sets whether warnings should be treated as errors. - */ - void setWarningsAsErrors(boolean value) { - mWarningsAsErrors = value; - } - - /** - * Returns errors encountered while verifying the APK's signatures. - */ - public List getErrors() { - if (!mWarningsAsErrors) { - return mErrors; - } else { - List allErrors = new ArrayList<>(); - allErrors.addAll(mErrors); - allErrors.addAll(mWarnings); - return allErrors; - } - } - - /** - * Returns warnings encountered while verifying the APK's signatures. - */ - public List getWarnings() { - return mWarnings; - } - - private void mergeFrom(V1SchemeVerifier.Result source) { - mVerifiedUsingV1Scheme = source.verified; - mErrors.addAll(source.getErrors()); - mWarnings.addAll(source.getWarnings()); - for (V1SchemeVerifier.Result.SignerInfo signer : source.signers) { - mV1SchemeSigners.add(new V1SchemeSignerInfo(signer)); - } - for (V1SchemeVerifier.Result.SignerInfo signer : source.ignoredSigners) { - mV1SchemeIgnoredSigners.add(new V1SchemeSignerInfo(signer)); - } - } - - private void mergeFrom(ApkSigResult source) { - switch (source.signatureSchemeVersion) { - case ApkSigningBlockUtils.VERSION_SOURCE_STAMP: - mSourceStampVerified = source.verified; - if (!source.mSigners.isEmpty()) { - mSourceStampInfo = new SourceStampInfo(source.mSigners.get(0)); - } - break; - default: - throw new IllegalArgumentException( - "Unknown ApkSigResult Signing Block Scheme Id " - + source.signatureSchemeVersion); - } - } - - private void mergeFrom(ApkSigningBlockUtils.Result source) { - switch (source.signatureSchemeVersion) { - case ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2: - mVerifiedUsingV2Scheme = source.verified; - for (ApkSigningBlockUtils.Result.SignerInfo signer : source.signers) { - mV2SchemeSigners.add(new V2SchemeSignerInfo(signer)); - } - break; - case ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3: - mVerifiedUsingV3Scheme = source.verified; - for (ApkSigningBlockUtils.Result.SignerInfo signer : source.signers) { - mV3SchemeSigners.add(new V3SchemeSignerInfo(signer)); - } - mSigningCertificateLineage = source.signingCertificateLineage; - break; - case ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V4: - mVerifiedUsingV4Scheme = source.verified; - for (ApkSigningBlockUtils.Result.SignerInfo signer : source.signers) { - mV4SchemeSigners.add(new V4SchemeSignerInfo(signer)); - } - break; - case ApkSigningBlockUtils.VERSION_SOURCE_STAMP: - mSourceStampVerified = source.verified; - if (!source.signers.isEmpty()) { - mSourceStampInfo = new SourceStampInfo(source.signers.get(0)); - } - break; - default: - throw new IllegalArgumentException("Unknown Signing Block Scheme Id"); - } - } - - /** - * Returns {@code true} if an error was encountered while verifying the APK. Any error - * prevents the APK from being considered verified. - */ - public boolean containsErrors() { - if (!mErrors.isEmpty()) { - return true; - } - if (mWarningsAsErrors && !mWarnings.isEmpty()) { - return true; - } - if (!mV1SchemeSigners.isEmpty()) { - for (V1SchemeSignerInfo signer : mV1SchemeSigners) { - if (signer.containsErrors()) { - return true; - } - if (mWarningsAsErrors && !signer.getWarnings().isEmpty()) { - return true; - } - } - } - if (!mV2SchemeSigners.isEmpty()) { - for (V2SchemeSignerInfo signer : mV2SchemeSigners) { - if (signer.containsErrors()) { - return true; - } - if (mWarningsAsErrors && !signer.getWarnings().isEmpty()) { - return true; - } - } - } - if (!mV3SchemeSigners.isEmpty()) { - for (V3SchemeSignerInfo signer : mV3SchemeSigners) { - if (signer.containsErrors()) { - return true; - } - if (mWarningsAsErrors && !signer.getWarnings().isEmpty()) { - return true; - } - } - } - if (mSourceStampInfo != null) { - if (mSourceStampInfo.containsErrors()) { - return true; - } - if (mWarningsAsErrors && !mSourceStampInfo.getWarnings().isEmpty()) { - return true; - } - } - - return false; - } - - /** - * Returns all errors for this result, including any errors from signature scheme signers - * and the source stamp. - */ - public List getAllErrors() { - List errors = new ArrayList<>(); - errors.addAll(mErrors); - if (mWarningsAsErrors) { - errors.addAll(mWarnings); - } - if (!mV1SchemeSigners.isEmpty()) { - for (V1SchemeSignerInfo signer : mV1SchemeSigners) { - errors.addAll(signer.mErrors); - if (mWarningsAsErrors) { - errors.addAll(signer.getWarnings()); - } - } - } - if (!mV2SchemeSigners.isEmpty()) { - for (V2SchemeSignerInfo signer : mV2SchemeSigners) { - errors.addAll(signer.mErrors); - if (mWarningsAsErrors) { - errors.addAll(signer.getWarnings()); - } - } - } - if (!mV3SchemeSigners.isEmpty()) { - for (V3SchemeSignerInfo signer : mV3SchemeSigners) { - errors.addAll(signer.mErrors); - if (mWarningsAsErrors) { - errors.addAll(signer.getWarnings()); - } - } - } - if (mSourceStampInfo != null) { - errors.addAll(mSourceStampInfo.getErrors()); - if (mWarningsAsErrors) { - errors.addAll(mSourceStampInfo.getWarnings()); - } - } - return errors; - } - - /** - * Information about a JAR signer associated with the APK's signature. - */ - public static class V1SchemeSignerInfo { - private final String mName; - private final List mCertChain; - private final String mSignatureBlockFileName; - private final String mSignatureFileName; - - private final List mErrors; - private final List mWarnings; - - private V1SchemeSignerInfo(V1SchemeVerifier.Result.SignerInfo result) { - mName = result.name; - mCertChain = result.certChain; - mSignatureBlockFileName = result.signatureBlockFileName; - mSignatureFileName = result.signatureFileName; - mErrors = result.getErrors(); - mWarnings = result.getWarnings(); - } - - /** - * Returns a user-friendly name of the signer. - */ - public String getName() { - return mName; - } - - /** - * Returns the name of the JAR entry containing this signer's JAR signature block file. - */ - public String getSignatureBlockFileName() { - return mSignatureBlockFileName; - } - - /** - * Returns the name of the JAR entry containing this signer's JAR signature file. - */ - public String getSignatureFileName() { - return mSignatureFileName; - } - - /** - * Returns this signer's signing certificate or {@code null} if not available. The - * certificate is guaranteed to be available if no errors were encountered during - * verification (see {@link #containsErrors()}. - * - *

This certificate contains the signer's public key. - */ - public X509Certificate getCertificate() { - return mCertChain.isEmpty() ? null : mCertChain.get(0); - } - - /** - * Returns the certificate chain for the signer's public key. The certificate containing - * the public key is first, followed by the certificate (if any) which issued the - * signing certificate, and so forth. An empty list may be returned if an error was - * encountered during verification (see {@link #containsErrors()}). - */ - public List getCertificateChain() { - return mCertChain; - } - - /** - * Returns {@code true} if an error was encountered while verifying this signer's JAR - * signature. Any error prevents the signer's signature from being considered verified. - */ - public boolean containsErrors() { - return !mErrors.isEmpty(); - } - - /** - * Returns errors encountered while verifying this signer's JAR signature. Any error - * prevents the signer's signature from being considered verified. - */ - public List getErrors() { - return mErrors; - } - - /** - * Returns warnings encountered while verifying this signer's JAR signature. Warnings - * do not prevent the signer's signature from being considered verified. - */ - public List getWarnings() { - return mWarnings; - } - - private void addError(Issue msg, Object... parameters) { - mErrors.add(new IssueWithParams(msg, parameters)); - } - } - - /** - * Information about an APK Signature Scheme v2 signer associated with the APK's signature. - */ - public static class V2SchemeSignerInfo { - private final int mIndex; - private final List mCerts; - - private final List mErrors; - private final List mWarnings; - private final List - mContentDigests; - - private V2SchemeSignerInfo(ApkSigningBlockUtils.Result.SignerInfo result) { - mIndex = result.index; - mCerts = result.certs; - mErrors = result.getErrors(); - mWarnings = result.getWarnings(); - mContentDigests = result.contentDigests; - } - - /** - * Returns this signer's {@code 0}-based index in the list of signers contained in the - * APK's APK Signature Scheme v2 signature. - */ - public int getIndex() { - return mIndex; - } - - /** - * Returns this signer's signing certificate or {@code null} if not available. The - * certificate is guaranteed to be available if no errors were encountered during - * verification (see {@link #containsErrors()}. - * - *

This certificate contains the signer's public key. - */ - public X509Certificate getCertificate() { - return mCerts.isEmpty() ? null : mCerts.get(0); - } - - /** - * Returns this signer's certificates. The first certificate is for the signer's public - * key. An empty list may be returned if an error was encountered during verification - * (see {@link #containsErrors()}). - */ - public List getCertificates() { - return mCerts; - } - - private void addError(Issue msg, Object... parameters) { - mErrors.add(new IssueWithParams(msg, parameters)); - } - - public boolean containsErrors() { - return !mErrors.isEmpty(); - } - - public List getErrors() { - return mErrors; - } - - public List getWarnings() { - return mWarnings; - } - - public List getContentDigests() { - return mContentDigests; - } - } - - /** - * Information about an APK Signature Scheme v3 signer associated with the APK's signature. - */ - public static class V3SchemeSignerInfo { - private final int mIndex; - private final List mCerts; - - private final List mErrors; - private final List mWarnings; - private final List - mContentDigests; - - private V3SchemeSignerInfo(ApkSigningBlockUtils.Result.SignerInfo result) { - mIndex = result.index; - mCerts = result.certs; - mErrors = result.getErrors(); - mWarnings = result.getWarnings(); - mContentDigests = result.contentDigests; - } - - /** - * Returns this signer's {@code 0}-based index in the list of signers contained in the - * APK's APK Signature Scheme v3 signature. - */ - public int getIndex() { - return mIndex; - } - - /** - * Returns this signer's signing certificate or {@code null} if not available. The - * certificate is guaranteed to be available if no errors were encountered during - * verification (see {@link #containsErrors()}. - * - *

This certificate contains the signer's public key. - */ - public X509Certificate getCertificate() { - return mCerts.isEmpty() ? null : mCerts.get(0); - } - - /** - * Returns this signer's certificates. The first certificate is for the signer's public - * key. An empty list may be returned if an error was encountered during verification - * (see {@link #containsErrors()}). - */ - public List getCertificates() { - return mCerts; - } - - public boolean containsErrors() { - return !mErrors.isEmpty(); - } - - public List getErrors() { - return mErrors; - } - - public List getWarnings() { - return mWarnings; - } - - public List getContentDigests() { - return mContentDigests; - } - } - - /** - * Information about an APK Signature Scheme V4 signer associated with the APK's - * signature. - */ - public static class V4SchemeSignerInfo { - private final int mIndex; - private final List mCerts; - - private final List mErrors; - private final List mWarnings; - private final List - mContentDigests; - - private V4SchemeSignerInfo(ApkSigningBlockUtils.Result.SignerInfo result) { - mIndex = result.index; - mCerts = result.certs; - mErrors = result.getErrors(); - mWarnings = result.getWarnings(); - mContentDigests = result.contentDigests; - } - - /** - * Returns this signer's {@code 0}-based index in the list of signers contained in the - * APK's APK Signature Scheme v3 signature. - */ - public int getIndex() { - return mIndex; - } - - /** - * Returns this signer's signing certificate or {@code null} if not available. The - * certificate is guaranteed to be available if no errors were encountered during - * verification (see {@link #containsErrors()}. - * - *

This certificate contains the signer's public key. - */ - public X509Certificate getCertificate() { - return mCerts.isEmpty() ? null : mCerts.get(0); - } - - /** - * Returns this signer's certificates. The first certificate is for the signer's public - * key. An empty list may be returned if an error was encountered during verification - * (see {@link #containsErrors()}). - */ - public List getCertificates() { - return mCerts; - } - - public boolean containsErrors() { - return !mErrors.isEmpty(); - } - - public List getErrors() { - return mErrors; - } - - public List getWarnings() { - return mWarnings; - } - - public List getContentDigests() { - return mContentDigests; - } - } - - /** - * Information about SourceStamp associated with the APK's signature. - */ - public static class SourceStampInfo { - public enum SourceStampVerificationStatus { - /** The stamp is present and was successfully verified. */ - STAMP_VERIFIED, - /** The stamp is present but failed verification. */ - STAMP_VERIFICATION_FAILED, - /** The expected cert digest did not match the digest in the APK. */ - CERT_DIGEST_MISMATCH, - /** The stamp is not present at all. */ - STAMP_MISSING, - /** The stamp is at least partially present, but was not able to be verified. */ - STAMP_NOT_VERIFIED, - /** The stamp was not able to be verified due to an unexpected error. */ - VERIFICATION_ERROR - } - - private final List mCertificates; - private final List mCertificateLineage; - - private final List mErrors; - private final List mWarnings; - - private final SourceStampVerificationStatus mSourceStampVerificationStatus; - - private SourceStampInfo(ApkSignerInfo result) { - mCertificates = result.certs; - mCertificateLineage = result.certificateLineage; - mErrors = ApkVerificationIssueAdapter.getIssuesFromVerificationIssues( - result.getErrors()); - mWarnings = ApkVerificationIssueAdapter.getIssuesFromVerificationIssues( - result.getWarnings()); - if (mErrors.isEmpty() && mWarnings.isEmpty()) { - mSourceStampVerificationStatus = SourceStampVerificationStatus.STAMP_VERIFIED; - } else { - mSourceStampVerificationStatus = - SourceStampVerificationStatus.STAMP_VERIFICATION_FAILED; - } - } - - SourceStampInfo(SourceStampVerificationStatus sourceStampVerificationStatus) { - mCertificates = Collections.emptyList(); - mCertificateLineage = Collections.emptyList(); - mErrors = Collections.emptyList(); - mWarnings = Collections.emptyList(); - mSourceStampVerificationStatus = sourceStampVerificationStatus; - } - - /** - * Returns the SourceStamp's signing certificate or {@code null} if not available. The - * certificate is guaranteed to be available if no errors were encountered during - * verification (see {@link #containsErrors()}. - * - *

This certificate contains the SourceStamp's public key. - */ - public X509Certificate getCertificate() { - return mCertificates.isEmpty() ? null : mCertificates.get(0); - } - - /** - * Returns a list containing all of the certificates in the stamp certificate lineage. - */ - public List getCertificatesInLineage() { - return mCertificateLineage; - } - - public boolean containsErrors() { - return !mErrors.isEmpty(); - } - - public List getErrors() { - return mErrors; - } - - public List getWarnings() { - return mWarnings; - } - - /** - * Returns the reason for any source stamp verification failures, or {@code - * STAMP_VERIFIED} if the source stamp was successfully verified. - */ - public SourceStampVerificationStatus getSourceStampVerificationStatus() { - return mSourceStampVerificationStatus; - } - } - } - - /** - * Error or warning encountered while verifying an APK's signatures. - */ - public enum Issue { - - /** - * APK is not JAR-signed. - */ - JAR_SIG_NO_SIGNATURES("No JAR signatures"), - - /** - * APK does not contain any entries covered by JAR signatures. - */ - JAR_SIG_NO_SIGNED_ZIP_ENTRIES("No JAR entries covered by JAR signatures"), - - /** - * APK contains multiple entries with the same name. - * - *

    - *
  • Parameter 1: name ({@code String})
  • - *
- */ - JAR_SIG_DUPLICATE_ZIP_ENTRY("Duplicate entry: %1$s"), - - /** - * JAR manifest contains a section with a duplicate name. - * - *
    - *
  • Parameter 1: section name ({@code String})
  • - *
- */ - JAR_SIG_DUPLICATE_MANIFEST_SECTION("Duplicate section in META-INF/MANIFEST.MF: %1$s"), - - /** - * JAR manifest contains a section without a name. - * - *
    - *
  • Parameter 1: section index (1-based) ({@code Integer})
  • - *
- */ - JAR_SIG_UNNNAMED_MANIFEST_SECTION( - "Malformed META-INF/MANIFEST.MF: invidual section #%1$d does not have a name"), - - /** - * JAR signature file contains a section without a name. - * - *
    - *
  • Parameter 1: signature file name ({@code String})
  • - *
  • Parameter 2: section index (1-based) ({@code Integer})
  • - *
- */ - JAR_SIG_UNNNAMED_SIG_FILE_SECTION( - "Malformed %1$s: invidual section #%2$d does not have a name"), - - /** APK is missing the JAR manifest entry (META-INF/MANIFEST.MF). */ - JAR_SIG_NO_MANIFEST("Missing META-INF/MANIFEST.MF"), - - /** - * JAR manifest references an entry which is not there in the APK. - * - *
    - *
  • Parameter 1: entry name ({@code String})
  • - *
- */ - JAR_SIG_MISSING_ZIP_ENTRY_REFERENCED_IN_MANIFEST( - "%1$s entry referenced by META-INF/MANIFEST.MF not found in the APK"), - - /** - * JAR manifest does not list a digest for the specified entry. - * - *
    - *
  • Parameter 1: entry name ({@code String})
  • - *
- */ - JAR_SIG_NO_ZIP_ENTRY_DIGEST_IN_MANIFEST("No digest for %1$s in META-INF/MANIFEST.MF"), - - /** - * JAR signature does not list a digest for the specified entry. - * - *
    - *
  • Parameter 1: entry name ({@code String})
  • - *
  • Parameter 2: signature file name ({@code String})
  • - *
- */ - JAR_SIG_NO_ZIP_ENTRY_DIGEST_IN_SIG_FILE("No digest for %1$s in %2$s"), - - /** - * The specified JAR entry is not covered by JAR signature. - * - *
    - *
  • Parameter 1: entry name ({@code String})
  • - *
- */ - JAR_SIG_ZIP_ENTRY_NOT_SIGNED("%1$s entry not signed"), - - /** - * JAR signature uses different set of signers to protect the two specified ZIP entries. - * - *
    - *
  • Parameter 1: first entry name ({@code String})
  • - *
  • Parameter 2: first entry signer names ({@code List})
  • - *
  • Parameter 3: second entry name ({@code String})
  • - *
  • Parameter 4: second entry signer names ({@code List})
  • - *
- */ - JAR_SIG_ZIP_ENTRY_SIGNERS_MISMATCH( - "Entries %1$s and %3$s are signed with different sets of signers" - + " : <%2$s> vs <%4$s>"), - - /** - * Digest of the specified ZIP entry's data does not match the digest expected by the JAR - * signature. - * - *
    - *
  • Parameter 1: entry name ({@code String})
  • - *
  • Parameter 2: digest algorithm (e.g., SHA-256) ({@code String})
  • - *
  • Parameter 3: name of the entry in which the expected digest is specified - * ({@code String})
  • - *
  • Parameter 4: base64-encoded actual digest ({@code String})
  • - *
  • Parameter 5: base64-encoded expected digest ({@code String})
  • - *
- */ - JAR_SIG_ZIP_ENTRY_DIGEST_DID_NOT_VERIFY( - "%2$s digest of %1$s does not match the digest specified in %3$s" - + ". Expected: <%5$s>, actual: <%4$s>"), - - /** - * Digest of the JAR manifest main section did not verify. - * - *
    - *
  • Parameter 1: digest algorithm (e.g., SHA-256) ({@code String})
  • - *
  • Parameter 2: name of the entry in which the expected digest is specified - * ({@code String})
  • - *
  • Parameter 3: base64-encoded actual digest ({@code String})
  • - *
  • Parameter 4: base64-encoded expected digest ({@code String})
  • - *
- */ - JAR_SIG_MANIFEST_MAIN_SECTION_DIGEST_DID_NOT_VERIFY( - "%1$s digest of META-INF/MANIFEST.MF main section does not match the digest" - + " specified in %2$s. Expected: <%4$s>, actual: <%3$s>"), - - /** - * Digest of the specified JAR manifest section does not match the digest expected by the - * JAR signature. - * - *
    - *
  • Parameter 1: section name ({@code String})
  • - *
  • Parameter 2: digest algorithm (e.g., SHA-256) ({@code String})
  • - *
  • Parameter 3: name of the signature file in which the expected digest is specified - * ({@code String})
  • - *
  • Parameter 4: base64-encoded actual digest ({@code String})
  • - *
  • Parameter 5: base64-encoded expected digest ({@code String})
  • - *
- */ - JAR_SIG_MANIFEST_SECTION_DIGEST_DID_NOT_VERIFY( - "%2$s digest of META-INF/MANIFEST.MF section for %1$s does not match the digest" - + " specified in %3$s. Expected: <%5$s>, actual: <%4$s>"), - - /** - * JAR signature file does not contain the whole-file digest of the JAR manifest file. The - * digest speeds up verification of JAR signature. - * - *
    - *
  • Parameter 1: name of the signature file ({@code String})
  • - *
- */ - JAR_SIG_NO_MANIFEST_DIGEST_IN_SIG_FILE( - "%1$s does not specify digest of META-INF/MANIFEST.MF" - + ". This slows down verification."), - - /** - * APK is signed using APK Signature Scheme v2 or newer, but JAR signature file does not - * contain protections against stripping of these newer scheme signatures. - * - *
    - *
  • Parameter 1: name of the signature file ({@code String})
  • - *
- */ - JAR_SIG_NO_APK_SIG_STRIP_PROTECTION( - "APK is signed using APK Signature Scheme v2 but these signatures may be stripped" - + " without being detected because %1$s does not contain anti-stripping" - + " protections."), - - /** - * JAR signature of the signer is missing a file/entry. - * - *
    - *
  • Parameter 1: name of the encountered file ({@code String})
  • - *
  • Parameter 2: name of the missing file ({@code String})
  • - *
- */ - JAR_SIG_MISSING_FILE("Partial JAR signature. Found: %1$s, missing: %2$s"), - - /** - * An exception was encountered while verifying JAR signature contained in a signature block - * against the signature file. - * - *
    - *
  • Parameter 1: name of the signature block file ({@code String})
  • - *
  • Parameter 2: name of the signature file ({@code String})
  • - *
  • Parameter 3: exception ({@code Throwable})
  • - *
- */ - JAR_SIG_VERIFY_EXCEPTION("Failed to verify JAR signature %1$s against %2$s: %3$s"), - - /** - * JAR signature contains unsupported digest algorithm. - * - *
    - *
  • Parameter 1: name of the signature block file ({@code String})
  • - *
  • Parameter 2: digest algorithm OID ({@code String})
  • - *
  • Parameter 3: signature algorithm OID ({@code String})
  • - *
  • Parameter 4: API Levels on which this combination of algorithms is not supported - * ({@code String})
  • - *
  • Parameter 5: user-friendly variant of digest algorithm ({@code String})
  • - *
  • Parameter 6: user-friendly variant of signature algorithm ({@code String})
  • - *
- */ - JAR_SIG_UNSUPPORTED_SIG_ALG( - "JAR signature %1$s uses digest algorithm %5$s and signature algorithm %6$s which" - + " is not supported on API Level(s) %4$s for which this APK is being" - + " verified"), - - /** - * An exception was encountered while parsing JAR signature contained in a signature block. - * - *
    - *
  • Parameter 1: name of the signature block file ({@code String})
  • - *
  • Parameter 2: exception ({@code Throwable})
  • - *
- */ - JAR_SIG_PARSE_EXCEPTION("Failed to parse JAR signature %1$s: %2$s"), - - /** - * An exception was encountered while parsing a certificate contained in the JAR signature - * block. - * - *
    - *
  • Parameter 1: name of the signature block file ({@code String})
  • - *
  • Parameter 2: exception ({@code Throwable})
  • - *
- */ - JAR_SIG_MALFORMED_CERTIFICATE("Malformed certificate in JAR signature %1$s: %2$s"), - - /** - * JAR signature contained in a signature block file did not verify against the signature - * file. - * - *
    - *
  • Parameter 1: name of the signature block file ({@code String})
  • - *
  • Parameter 2: name of the signature file ({@code String})
  • - *
- */ - JAR_SIG_DID_NOT_VERIFY("JAR signature %1$s did not verify against %2$s"), - - /** - * JAR signature contains no verified signers. - * - *
    - *
  • Parameter 1: name of the signature block file ({@code String})
  • - *
- */ - JAR_SIG_NO_SIGNERS("JAR signature %1$s contains no signers"), - - /** - * JAR signature file contains a section with a duplicate name. - * - *
    - *
  • Parameter 1: signature file name ({@code String})
  • - *
  • Parameter 1: section name ({@code String})
  • - *
- */ - JAR_SIG_DUPLICATE_SIG_FILE_SECTION("Duplicate section in %1$s: %2$s"), - - /** - * JAR signature file's main section doesn't contain the mandatory Signature-Version - * attribute. - * - *
    - *
  • Parameter 1: signature file name ({@code String})
  • - *
- */ - JAR_SIG_MISSING_VERSION_ATTR_IN_SIG_FILE( - "Malformed %1$s: missing Signature-Version attribute"), - - /** - * JAR signature file references an unknown APK signature scheme ID. - * - *
    - *
  • Parameter 1: name of the signature file ({@code String})
  • - *
  • Parameter 2: unknown APK signature scheme ID ({@code} Integer)
  • - *
- */ - JAR_SIG_UNKNOWN_APK_SIG_SCHEME_ID( - "JAR signature %1$s references unknown APK signature scheme ID: %2$d"), - - /** - * JAR signature file indicates that the APK is supposed to be signed with a supported APK - * signature scheme (in addition to the JAR signature) but no such signature was found in - * the APK. - * - *
    - *
  • Parameter 1: name of the signature file ({@code String})
  • - *
  • Parameter 2: APK signature scheme ID ({@code} Integer)
  • - *
  • Parameter 3: APK signature scheme English name ({@code} String)
  • - *
- */ - JAR_SIG_MISSING_APK_SIG_REFERENCED( - "JAR signature %1$s indicates the APK is signed using %3$s but no such signature" - + " was found. Signature stripped?"), - - /** - * JAR entry is not covered by signature and thus unauthorized modifications to its contents - * will not be detected. - * - *
    - *
  • Parameter 1: entry name ({@code String})
  • - *
- */ - JAR_SIG_UNPROTECTED_ZIP_ENTRY( - "%1$s not protected by signature. Unauthorized modifications to this JAR entry" - + " will not be detected. Delete or move the entry outside of META-INF/."), - - /** - * APK which is both JAR-signed and signed using APK Signature Scheme v2 contains an APK - * Signature Scheme v2 signature from this signer, but does not contain a JAR signature - * from this signer. - */ - JAR_SIG_MISSING("No JAR signature from this signer"), - - /** - * APK is targeting a sandbox version which requires APK Signature Scheme v2 signature but - * no such signature was found. - * - *
    - *
  • Parameter 1: target sandbox version ({@code Integer})
  • - *
- */ - NO_SIG_FOR_TARGET_SANDBOX_VERSION( - "Missing APK Signature Scheme v2 signature required for target sandbox version" - + " %1$d"), - - /** - * APK is targeting an SDK version that requires a minimum signature scheme version, but the - * APK is not signed with that version or later. - * - *
    - *
  • Parameter 1: target SDK Version (@code Integer})
  • - *
  • Parameter 2: minimum signature scheme version ((@code Integer})
  • - *
- */ - MIN_SIG_SCHEME_FOR_TARGET_SDK_NOT_MET( - "Target SDK version %1$d requires a minimum of signature scheme v%2$d; the APK is" - + " not signed with this or a later signature scheme"), - - /** - * APK which is both JAR-signed and signed using APK Signature Scheme v2 contains a JAR - * signature from this signer, but does not contain an APK Signature Scheme v2 signature - * from this signer. - */ - V2_SIG_MISSING("No APK Signature Scheme v2 signature from this signer"), - - /** - * Failed to parse the list of signers contained in the APK Signature Scheme v2 signature. - */ - V2_SIG_MALFORMED_SIGNERS("Malformed list of signers"), - - /** - * Failed to parse this signer's signer block contained in the APK Signature Scheme v2 - * signature. - */ - V2_SIG_MALFORMED_SIGNER("Malformed signer block"), - - /** - * Public key embedded in the APK Signature Scheme v2 signature of this signer could not be - * parsed. - * - *
    - *
  • Parameter 1: error details ({@code Throwable})
  • - *
- */ - V2_SIG_MALFORMED_PUBLIC_KEY("Malformed public key: %1$s"), - - /** - * This APK Signature Scheme v2 signer's certificate could not be parsed. - * - *
    - *
  • Parameter 1: index ({@code 0}-based) of the certificate in the signer's list of - * certificates ({@code Integer})
  • - *
  • Parameter 2: sequence number ({@code 1}-based) of the certificate in the signer's - * list of certificates ({@code Integer})
  • - *
  • Parameter 3: error details ({@code Throwable})
  • - *
- */ - V2_SIG_MALFORMED_CERTIFICATE("Malformed certificate #%2$d: %3$s"), - - /** - * Failed to parse this signer's signature record contained in the APK Signature Scheme v2 - * signature. - * - *
    - *
  • Parameter 1: record number (first record is {@code 1}) ({@code Integer})
  • - *
- */ - V2_SIG_MALFORMED_SIGNATURE("Malformed APK Signature Scheme v2 signature record #%1$d"), - - /** - * Failed to parse this signer's digest record contained in the APK Signature Scheme v2 - * signature. - * - *
    - *
  • Parameter 1: record number (first record is {@code 1}) ({@code Integer})
  • - *
- */ - V2_SIG_MALFORMED_DIGEST("Malformed APK Signature Scheme v2 digest record #%1$d"), - - /** - * This APK Signature Scheme v2 signer contains a malformed additional attribute. - * - *
    - *
  • Parameter 1: attribute number (first attribute is {@code 1}) {@code Integer})
  • - *
- */ - V2_SIG_MALFORMED_ADDITIONAL_ATTRIBUTE("Malformed additional attribute #%1$d"), - - /** - * APK Signature Scheme v2 signature references an unknown APK signature scheme ID. - * - *
    - *
  • Parameter 1: signer index ({@code Integer})
  • - *
  • Parameter 2: unknown APK signature scheme ID ({@code} Integer)
  • - *
- */ - V2_SIG_UNKNOWN_APK_SIG_SCHEME_ID( - "APK Signature Scheme v2 signer: %1$s references unknown APK signature scheme ID: " - + "%2$d"), - - /** - * APK Signature Scheme v2 signature indicates that the APK is supposed to be signed with a - * supported APK signature scheme (in addition to the v2 signature) but no such signature - * was found in the APK. - * - *
    - *
  • Parameter 1: signer index ({@code Integer})
  • - *
  • Parameter 2: APK signature scheme English name ({@code} String)
  • - *
- */ - V2_SIG_MISSING_APK_SIG_REFERENCED( - "APK Signature Scheme v2 signature %1$s indicates the APK is signed using %2$s but " - + "no such signature was found. Signature stripped?"), - - /** - * APK Signature Scheme v2 signature contains no signers. - */ - V2_SIG_NO_SIGNERS("No signers in APK Signature Scheme v2 signature"), - - /** - * This APK Signature Scheme v2 signer contains a signature produced using an unknown - * algorithm. - * - *
    - *
  • Parameter 1: algorithm ID ({@code Integer})
  • - *
- */ - V2_SIG_UNKNOWN_SIG_ALGORITHM("Unknown signature algorithm: %1$#x"), - - /** - * This APK Signature Scheme v2 signer contains an unknown additional attribute. - * - *
    - *
  • Parameter 1: attribute ID ({@code Integer})
  • - *
- */ - V2_SIG_UNKNOWN_ADDITIONAL_ATTRIBUTE("Unknown additional attribute: ID %1$#x"), - - /** - * An exception was encountered while verifying APK Signature Scheme v2 signature of this - * signer. - * - *
    - *
  • Parameter 1: signature algorithm ({@link SignatureAlgorithm})
  • - *
  • Parameter 2: exception ({@code Throwable})
  • - *
- */ - V2_SIG_VERIFY_EXCEPTION("Failed to verify %1$s signature: %2$s"), - - /** - * APK Signature Scheme v2 signature over this signer's signed-data block did not verify. - * - *
    - *
  • Parameter 1: signature algorithm ({@link SignatureAlgorithm})
  • - *
- */ - V2_SIG_DID_NOT_VERIFY("%1$s signature over signed-data did not verify"), - - /** - * This APK Signature Scheme v2 signer offers no signatures. - */ - V2_SIG_NO_SIGNATURES("No signatures"), - - /** - * This APK Signature Scheme v2 signer offers signatures but none of them are supported. - */ - V2_SIG_NO_SUPPORTED_SIGNATURES("No supported signatures: %1$s"), - - /** - * This APK Signature Scheme v2 signer offers no certificates. - */ - V2_SIG_NO_CERTIFICATES("No certificates"), - - /** - * This APK Signature Scheme v2 signer's public key listed in the signer's certificate does - * not match the public key listed in the signatures record. - * - *
    - *
  • Parameter 1: hex-encoded public key from certificate ({@code String})
  • - *
  • Parameter 2: hex-encoded public key from signatures record ({@code String})
  • - *
- */ - V2_SIG_PUBLIC_KEY_MISMATCH_BETWEEN_CERTIFICATE_AND_SIGNATURES_RECORD( - "Public key mismatch between certificate and signature record: <%1$s> vs <%2$s>"), - - /** - * This APK Signature Scheme v2 signer's signature algorithms listed in the signatures - * record do not match the signature algorithms listed in the signatures record. - * - *
    - *
  • Parameter 1: signature algorithms from signatures record ({@code List})
  • - *
  • Parameter 2: signature algorithms from digests record ({@code List})
  • - *
- */ - V2_SIG_SIG_ALG_MISMATCH_BETWEEN_SIGNATURES_AND_DIGESTS_RECORDS( - "Signature algorithms mismatch between signatures and digests records" - + ": %1$s vs %2$s"), - - /** - * The APK's digest does not match the digest contained in the APK Signature Scheme v2 - * signature. - * - *
    - *
  • Parameter 1: content digest algorithm ({@link ContentDigestAlgorithm})
  • - *
  • Parameter 2: hex-encoded expected digest of the APK ({@code String})
  • - *
  • Parameter 3: hex-encoded actual digest of the APK ({@code String})
  • - *
- */ - V2_SIG_APK_DIGEST_DID_NOT_VERIFY( - "APK integrity check failed. %1$s digest mismatch." - + " Expected: <%2$s>, actual: <%3$s>"), - - /** - * Failed to parse the list of signers contained in the APK Signature Scheme v3 signature. - */ - V3_SIG_MALFORMED_SIGNERS("Malformed list of signers"), - - /** - * Failed to parse this signer's signer block contained in the APK Signature Scheme v3 - * signature. - */ - V3_SIG_MALFORMED_SIGNER("Malformed signer block"), - - /** - * Public key embedded in the APK Signature Scheme v3 signature of this signer could not be - * parsed. - * - *
    - *
  • Parameter 1: error details ({@code Throwable})
  • - *
- */ - V3_SIG_MALFORMED_PUBLIC_KEY("Malformed public key: %1$s"), - - /** - * This APK Signature Scheme v3 signer's certificate could not be parsed. - * - *
    - *
  • Parameter 1: index ({@code 0}-based) of the certificate in the signer's list of - * certificates ({@code Integer})
  • - *
  • Parameter 2: sequence number ({@code 1}-based) of the certificate in the signer's - * list of certificates ({@code Integer})
  • - *
  • Parameter 3: error details ({@code Throwable})
  • - *
- */ - V3_SIG_MALFORMED_CERTIFICATE("Malformed certificate #%2$d: %3$s"), - - /** - * Failed to parse this signer's signature record contained in the APK Signature Scheme v3 - * signature. - * - *
    - *
  • Parameter 1: record number (first record is {@code 1}) ({@code Integer})
  • - *
- */ - V3_SIG_MALFORMED_SIGNATURE("Malformed APK Signature Scheme v3 signature record #%1$d"), - - /** - * Failed to parse this signer's digest record contained in the APK Signature Scheme v3 - * signature. - * - *
    - *
  • Parameter 1: record number (first record is {@code 1}) ({@code Integer})
  • - *
- */ - V3_SIG_MALFORMED_DIGEST("Malformed APK Signature Scheme v3 digest record #%1$d"), - - /** - * This APK Signature Scheme v3 signer contains a malformed additional attribute. - * - *
    - *
  • Parameter 1: attribute number (first attribute is {@code 1}) {@code Integer})
  • - *
- */ - V3_SIG_MALFORMED_ADDITIONAL_ATTRIBUTE("Malformed additional attribute #%1$d"), - - /** - * APK Signature Scheme v3 signature contains no signers. - */ - V3_SIG_NO_SIGNERS("No signers in APK Signature Scheme v3 signature"), - - /** - * APK Signature Scheme v3 signature contains multiple signers (only one allowed per - * platform version). - */ - V3_SIG_MULTIPLE_SIGNERS("Multiple APK Signature Scheme v3 signatures found for a single " - + " platform version."), - - /** - * APK Signature Scheme v3 signature found, but multiple v1 and/or multiple v2 signers - * found, where only one may be used with APK Signature Scheme v3 - */ - V3_SIG_MULTIPLE_PAST_SIGNERS("Multiple signatures found for pre-v3 signing with an APK " - + " Signature Scheme v3 signer. Only one allowed."), - - /** - * APK Signature Scheme v3 signature found, but its signer doesn't match the v1/v2 signers, - * or have them as the root of its signing certificate history - */ - V3_SIG_PAST_SIGNERS_MISMATCH( - "v3 signer differs from v1/v2 signer without proper signing certificate lineage."), - - /** - * This APK Signature Scheme v3 signer contains a signature produced using an unknown - * algorithm. - * - *
    - *
  • Parameter 1: algorithm ID ({@code Integer})
  • - *
- */ - V3_SIG_UNKNOWN_SIG_ALGORITHM("Unknown signature algorithm: %1$#x"), - - /** - * This APK Signature Scheme v3 signer contains an unknown additional attribute. - * - *
    - *
  • Parameter 1: attribute ID ({@code Integer})
  • - *
- */ - V3_SIG_UNKNOWN_ADDITIONAL_ATTRIBUTE("Unknown additional attribute: ID %1$#x"), - - /** - * An exception was encountered while verifying APK Signature Scheme v3 signature of this - * signer. - * - *
    - *
  • Parameter 1: signature algorithm ({@link SignatureAlgorithm})
  • - *
  • Parameter 2: exception ({@code Throwable})
  • - *
- */ - V3_SIG_VERIFY_EXCEPTION("Failed to verify %1$s signature: %2$s"), - - /** - * The APK Signature Scheme v3 signer contained an invalid value for either min or max SDK - * versions. - * - *
    - *
  • Parameter 1: minSdkVersion ({@code Integer}) - *
  • Parameter 2: maxSdkVersion ({@code Integer}) - *
- */ - V3_SIG_INVALID_SDK_VERSIONS("Invalid SDK Version parameter(s) encountered in APK Signature " - + "scheme v3 signature: minSdkVersion %1$s maxSdkVersion: %2$s"), - - /** - * APK Signature Scheme v3 signature over this signer's signed-data block did not verify. - * - *
    - *
  • Parameter 1: signature algorithm ({@link SignatureAlgorithm})
  • - *
- */ - V3_SIG_DID_NOT_VERIFY("%1$s signature over signed-data did not verify"), - - /** - * This APK Signature Scheme v3 signer offers no signatures. - */ - V3_SIG_NO_SIGNATURES("No signatures"), - - /** - * This APK Signature Scheme v3 signer offers signatures but none of them are supported. - */ - V3_SIG_NO_SUPPORTED_SIGNATURES("No supported signatures"), - - /** - * This APK Signature Scheme v3 signer offers no certificates. - */ - V3_SIG_NO_CERTIFICATES("No certificates"), - - /** - * This APK Signature Scheme v3 signer's minSdkVersion listed in the signer's signed data - * does not match the minSdkVersion listed in the signatures record. - * - *
    - *
  • Parameter 1: minSdkVersion in signature record ({@code Integer})
  • - *
  • Parameter 2: minSdkVersion in signed data ({@code Integer})
  • - *
- */ - V3_MIN_SDK_VERSION_MISMATCH_BETWEEN_SIGNER_AND_SIGNED_DATA_RECORD( - "minSdkVersion mismatch between signed data and signature record:" - + " <%1$s> vs <%2$s>"), - - /** - * This APK Signature Scheme v3 signer's maxSdkVersion listed in the signer's signed data - * does not match the maxSdkVersion listed in the signatures record. - * - *
    - *
  • Parameter 1: maxSdkVersion in signature record ({@code Integer})
  • - *
  • Parameter 2: maxSdkVersion in signed data ({@code Integer})
  • - *
- */ - V3_MAX_SDK_VERSION_MISMATCH_BETWEEN_SIGNER_AND_SIGNED_DATA_RECORD( - "maxSdkVersion mismatch between signed data and signature record:" - + " <%1$s> vs <%2$s>"), - - /** - * This APK Signature Scheme v3 signer's public key listed in the signer's certificate does - * not match the public key listed in the signatures record. - * - *
    - *
  • Parameter 1: hex-encoded public key from certificate ({@code String})
  • - *
  • Parameter 2: hex-encoded public key from signatures record ({@code String})
  • - *
- */ - V3_SIG_PUBLIC_KEY_MISMATCH_BETWEEN_CERTIFICATE_AND_SIGNATURES_RECORD( - "Public key mismatch between certificate and signature record: <%1$s> vs <%2$s>"), - - /** - * This APK Signature Scheme v3 signer's signature algorithms listed in the signatures - * record do not match the signature algorithms listed in the signatures record. - * - *
    - *
  • Parameter 1: signature algorithms from signatures record ({@code List})
  • - *
  • Parameter 2: signature algorithms from digests record ({@code List})
  • - *
- */ - V3_SIG_SIG_ALG_MISMATCH_BETWEEN_SIGNATURES_AND_DIGESTS_RECORDS( - "Signature algorithms mismatch between signatures and digests records" - + ": %1$s vs %2$s"), - - /** - * The APK's digest does not match the digest contained in the APK Signature Scheme v3 - * signature. - * - *
    - *
  • Parameter 1: content digest algorithm ({@link ContentDigestAlgorithm})
  • - *
  • Parameter 2: hex-encoded expected digest of the APK ({@code String})
  • - *
  • Parameter 3: hex-encoded actual digest of the APK ({@code String})
  • - *
- */ - V3_SIG_APK_DIGEST_DID_NOT_VERIFY( - "APK integrity check failed. %1$s digest mismatch." - + " Expected: <%2$s>, actual: <%3$s>"), - - /** - * The signer's SigningCertificateLineage attribute containd a proof-of-rotation record with - * signature(s) that did not verify. - */ - V3_SIG_POR_DID_NOT_VERIFY("SigningCertificateLineage attribute containd a proof-of-rotation" - + " record with signature(s) that did not verify."), - - /** - * Failed to parse the SigningCertificateLineage structure in the APK Signature Scheme v3 - * signature's additional attributes section. - */ - V3_SIG_MALFORMED_LINEAGE("Failed to parse the SigningCertificateLineage structure in the " - + "APK Signature Scheme v3 signature's additional attributes section."), - - /** - * The APK's signing certificate does not match the terminal node in the provided - * proof-of-rotation structure describing the signing certificate history - */ - V3_SIG_POR_CERT_MISMATCH( - "APK signing certificate differs from the associated certificate found in the " - + "signer's SigningCertificateLineage."), - - /** - * The APK Signature Scheme v3 signers encountered do not offer a continuous set of - * supported platform versions. Either they overlap, resulting in potentially two - * acceptable signers for a platform version, or there are holes which would create problems - * in the event of platform version upgrades. - */ - V3_INCONSISTENT_SDK_VERSIONS("APK Signature Scheme v3 signers supported min/max SDK " - + "versions are not continuous."), - - /** - * The APK Signature Scheme v3 signers don't cover all requested SDK versions. - * - *
    - *
  • Parameter 1: minSdkVersion ({@code Integer}) - *
  • Parameter 2: maxSdkVersion ({@code Integer}) - *
- */ - V3_MISSING_SDK_VERSIONS("APK Signature Scheme v3 signers supported min/max SDK " - + "versions do not cover the entire desired range. Found min: %1$s max %2$s"), - - /** - * The SigningCertificateLineages for different platform versions using APK Signature Scheme - * v3 do not go together. Specifically, each should be a subset of another, with the size - * of each increasing as the platform level increases. - */ - V3_INCONSISTENT_LINEAGES("SigningCertificateLineages targeting different platform versions" - + " using APK Signature Scheme v3 are not all a part of the same overall lineage."), - - /** - * APK Signing Block contains an unknown entry. - * - *
    - *
  • Parameter 1: entry ID ({@code Integer})
  • - *
- */ - APK_SIG_BLOCK_UNKNOWN_ENTRY_ID("APK Signing Block contains unknown entry: ID %1$#x"), - - /** - * Failed to parse this signer's signature record contained in the APK Signature Scheme - * V4 signature. - * - *
    - *
  • Parameter 1: record number (first record is {@code 1}) ({@code Integer})
  • - *
- */ - V4_SIG_MALFORMED_SIGNERS( - "V4 signature has malformed signer block"), - - /** - * This APK Signature Scheme V4 signer contains a signature produced using an - * unknown algorithm. - * - *
    - *
  • Parameter 1: algorithm ID ({@code Integer})
  • - *
- */ - V4_SIG_UNKNOWN_SIG_ALGORITHM( - "V4 signature has unknown signing algorithm: %1$#x"), - - /** - * This APK Signature Scheme V4 signer offers no signatures. - */ - V4_SIG_NO_SIGNATURES( - "V4 signature has no signature found"), - - /** - * This APK Signature Scheme V4 signer offers signatures but none of them are - * supported. - */ - V4_SIG_NO_SUPPORTED_SIGNATURES( - "V4 signature has no supported signature"), - - /** - * APK Signature Scheme v3 signature over this signer's signed-data block did not verify. - * - *
    - *
  • Parameter 1: signature algorithm ({@link SignatureAlgorithm})
  • - *
- */ - V4_SIG_DID_NOT_VERIFY("%1$s signature over signed-data did not verify"), - - /** - * An exception was encountered while verifying APK Signature Scheme v3 signature of this - * signer. - * - *
    - *
  • Parameter 1: signature algorithm ({@link SignatureAlgorithm})
  • - *
  • Parameter 2: exception ({@code Throwable})
  • - *
- */ - V4_SIG_VERIFY_EXCEPTION("Failed to verify %1$s signature: %2$s"), - - /** - * Public key embedded in the APK Signature Scheme v4 signature of this signer could not be - * parsed. - * - *
    - *
  • Parameter 1: error details ({@code Throwable})
  • - *
- */ - V4_SIG_MALFORMED_PUBLIC_KEY("Malformed public key: %1$s"), - - /** - * This APK Signature Scheme V4 signer's certificate could not be parsed. - * - *
    - *
  • Parameter 1: index ({@code 0}-based) of the certificate in the signer's list of - * certificates ({@code Integer})
  • - *
  • Parameter 2: sequence number ({@code 1}-based) of the certificate in the signer's - * list of certificates ({@code Integer})
  • - *
  • Parameter 3: error details ({@code Throwable})
  • - *
- */ - V4_SIG_MALFORMED_CERTIFICATE( - "V4 signature has malformed certificate"), - - /** - * This APK Signature Scheme V4 signer offers no certificate. - */ - V4_SIG_NO_CERTIFICATE("V4 signature has no certificate"), - - /** - * This APK Signature Scheme V4 signer's public key listed in the signer's - * certificate does not match the public key listed in the signature proto. - * - *
    - *
  • Parameter 1: hex-encoded public key from certificate ({@code String})
  • - *
  • Parameter 2: hex-encoded public key from signature proto ({@code String})
  • - *
- */ - V4_SIG_PUBLIC_KEY_MISMATCH_BETWEEN_CERTIFICATE_AND_SIGNATURES_RECORD( - "V4 signature has mismatched certificate and signature: <%1$s> vs <%2$s>"), - - /** - * The APK's hash root (aka digest) does not match the hash root contained in the Signature - * Scheme V4 signature. - * - *
    - *
  • Parameter 1: content digest algorithm ({@link ContentDigestAlgorithm})
  • - *
  • Parameter 2: hex-encoded expected digest of the APK ({@code String})
  • - *
  • Parameter 3: hex-encoded actual digest of the APK ({@code String})
  • - *
- */ - V4_SIG_APK_ROOT_DID_NOT_VERIFY( - "V4 signature's hash tree root (content digest) did not verity"), - - /** - * The APK's hash tree does not match the hash tree contained in the Signature - * Scheme V4 signature. - * - *
    - *
  • Parameter 1: content digest algorithm ({@link ContentDigestAlgorithm})
  • - *
  • Parameter 2: hex-encoded expected hash tree of the APK ({@code String})
  • - *
  • Parameter 3: hex-encoded actual hash tree of the APK ({@code String})
  • - *
- */ - V4_SIG_APK_TREE_DID_NOT_VERIFY( - "V4 signature's hash tree did not verity"), - - /** - * Using more than one Signer to sign APK Signature Scheme V4 signature. - */ - V4_SIG_MULTIPLE_SIGNERS( - "V4 signature only supports one signer"), - - /** - * The signer used to sign APK Signature Scheme V2/V3 signature does not match the signer - * used to sign APK Signature Scheme V4 signature. - */ - V4_SIG_V2_V3_SIGNERS_MISMATCH( - "V4 signature and V2/V3 signature have mismatched certificates"), - - V4_SIG_V2_V3_DIGESTS_MISMATCH( - "V4 signature and V2/V3 signature have mismatched digests"), - - /** - * The v4 signature format version isn't the same as the tool's current version, something - * may go wrong. - */ - V4_SIG_VERSION_NOT_CURRENT( - "V4 signature format version %1$d is different from the tool's current " - + "version %2$d"), - - /** - * The APK does not contain the source stamp certificate digest file nor the signature block - * when verification expected a source stamp to be present. - */ - SOURCE_STAMP_CERT_DIGEST_AND_SIG_BLOCK_MISSING( - "Neither the source stamp certificate digest file nor the signature block are " - + "present in the APK"), - - /** APK contains SourceStamp file, but does not contain a SourceStamp signature. */ - SOURCE_STAMP_SIG_MISSING("No SourceStamp signature"), - - /** - * SourceStamp's certificate could not be parsed. - * - *
    - *
  • Parameter 1: error details ({@code Throwable}) - *
- */ - SOURCE_STAMP_MALFORMED_CERTIFICATE("Malformed certificate: %1$s"), - - /** Failed to parse SourceStamp's signature. */ - SOURCE_STAMP_MALFORMED_SIGNATURE("Malformed SourceStamp signature"), - - /** - * SourceStamp contains a signature produced using an unknown algorithm. - * - *
    - *
  • Parameter 1: algorithm ID ({@code Integer}) - *
- */ - SOURCE_STAMP_UNKNOWN_SIG_ALGORITHM("Unknown signature algorithm: %1$#x"), - - /** - * An exception was encountered while verifying SourceStamp signature. - * - *
    - *
  • Parameter 1: signature algorithm ({@link SignatureAlgorithm}) - *
  • Parameter 2: exception ({@code Throwable}) - *
- */ - SOURCE_STAMP_VERIFY_EXCEPTION("Failed to verify %1$s signature: %2$s"), - - /** - * SourceStamp signature block did not verify. - * - *
    - *
  • Parameter 1: signature algorithm ({@link SignatureAlgorithm}) - *
- */ - SOURCE_STAMP_DID_NOT_VERIFY("%1$s signature over signed-data did not verify"), - - /** SourceStamp offers no signatures. */ - SOURCE_STAMP_NO_SIGNATURE("No signature"), - - /** - * SourceStamp offers an unsupported signature. - *
    - *
  • Parameter 1: list of {@link SignatureAlgorithm}s in the source stamp - * signing block. - *
  • Parameter 2: {@code Exception} caught when attempting to obtain the list of - * supported signatures. - *
- */ - SOURCE_STAMP_NO_SUPPORTED_SIGNATURE("Signature(s) {%1$s} not supported: %2$s"), - - /** - * SourceStamp's certificate listed in the APK signing block does not match the certificate - * listed in the SourceStamp file in the APK. - * - *
    - *
  • Parameter 1: SHA-256 hash of certificate from SourceStamp block in APK signing - * block ({@code String}) - *
  • Parameter 2: SHA-256 hash of certificate from SourceStamp file in APK ({@code - * String}) - *
- */ - SOURCE_STAMP_CERTIFICATE_MISMATCH_BETWEEN_SIGNATURE_BLOCK_AND_APK( - "Certificate mismatch between SourceStamp block in APK signing block and" - + " SourceStamp file in APK: <%1$s> vs <%2$s>"), - - /** - * The APK contains a source stamp signature block without the expected certificate digest - * in the APK contents. - */ - SOURCE_STAMP_SIGNATURE_BLOCK_WITHOUT_CERT_DIGEST( - "A source stamp signature block was found without a corresponding certificate " - + "digest in the APK"), - - /** - * When verifying just the source stamp, the certificate digest in the APK does not match - * the expected digest. - *
    - *
  • Parameter 1: SHA-256 digest of the source stamp certificate in the APK. - *
  • Parameter 2: SHA-256 digest of the expected source stamp certificate. - *
- */ - SOURCE_STAMP_EXPECTED_DIGEST_MISMATCH( - "The source stamp certificate digest in the APK, %1$s, does not match the " - + "expected digest, %2$s"), - - /** - * Source stamp block contains a malformed attribute. - * - *
    - *
  • Parameter 1: attribute number (first attribute is {@code 1}) {@code Integer})
  • - *
- */ - SOURCE_STAMP_MALFORMED_ATTRIBUTE("Malformed stamp attribute #%1$d"), - - /** - * Source stamp block contains an unknown attribute. - * - *
    - *
  • Parameter 1: attribute ID ({@code Integer})
  • - *
- */ - SOURCE_STAMP_UNKNOWN_ATTRIBUTE("Unknown stamp attribute: ID %1$#x"), - - /** - * Failed to parse the SigningCertificateLineage structure in the source stamp - * attributes section. - */ - SOURCE_STAMP_MALFORMED_LINEAGE("Failed to parse the SigningCertificateLineage " - + "structure in the source stamp attributes section."), - - /** - * The source stamp certificate does not match the terminal node in the provided - * proof-of-rotation structure describing the stamp certificate history. - */ - SOURCE_STAMP_POR_CERT_MISMATCH( - "APK signing certificate differs from the associated certificate found in the " - + "signer's SigningCertificateLineage."), - - /** - * The source stamp SigningCertificateLineage attribute contains a proof-of-rotation record - * with signature(s) that did not verify. - */ - SOURCE_STAMP_POR_DID_NOT_VERIFY("Source stamp SigningCertificateLineage attribute " - + "contains a proof-of-rotation record with signature(s) that did not verify."), - - /** - * The APK could not be properly parsed due to a ZIP or APK format exception. - *
    - *
  • Parameter 1: The {@code Exception} caught when attempting to parse the APK. - *
- */ - MALFORMED_APK( - "Malformed APK; the following exception was caught when attempting to parse the " - + "APK: %1$s"), - - /** - * An unexpected exception was caught when attempting to verify the signature(s) within the - * APK. - *
    - *
  • Parameter 1: The {@code Exception} caught during verification. - *
- */ - UNEXPECTED_EXCEPTION( - "An unexpected exception was caught when verifying the signature: %1$s"); - - private final String mFormat; - - Issue(String format) { - mFormat = format; - } - - /** - * Returns the format string suitable for combining the parameters of this issue into a - * readable string. See {@link java.util.Formatter} for format. - */ - private String getFormat() { - return mFormat; - } - } - - /** - * {@link Issue} with associated parameters. {@link #toString()} produces a readable formatted - * form. - */ - public static class IssueWithParams extends ApkVerificationIssue { - private final Issue mIssue; - private final Object[] mParams; - - /** - * Constructs a new {@code IssueWithParams} of the specified type and with provided - * parameters. - */ - public IssueWithParams(Issue issue, Object[] params) { - super(issue.mFormat, params); - mIssue = issue; - mParams = params; - } - - /** - * Returns the type of this issue. - */ - public Issue getIssue() { - return mIssue; - } - - /** - * Returns the parameters of this issue. - */ - public Object[] getParams() { - return mParams.clone(); - } - - /** - * Returns a readable form of this issue. - */ - @Override - public String toString() { - return String.format(mIssue.getFormat(), mParams); - } - } - - /** - * Wrapped around {@code byte[]} which ensures that {@code equals} and {@code hashCode} operate - * on the contents of the arrays rather than on references. - */ - private static class ByteArray { - private final byte[] mArray; - private final int mHashCode; - - private ByteArray(byte[] arr) { - mArray = arr; - mHashCode = Arrays.hashCode(mArray); - } - - @Override - public int hashCode() { - return mHashCode; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof ByteArray)) { - return false; - } - ByteArray other = (ByteArray) obj; - if (hashCode() != other.hashCode()) { - return false; - } - if (!Arrays.equals(mArray, other.mArray)) { - return false; - } - return true; - } - } - - /** - * Builder of {@link ApkVerifier} instances. - * - *

The resulting verifier by default checks whether the APK will verify on all platform - * versions supported by the APK, as specified by {@code android:minSdkVersion} attributes in - * the APK's {@code AndroidManifest.xml}. The range of platform versions can be customized using - * {@link #setMinCheckedPlatformVersion(int)} and {@link #setMaxCheckedPlatformVersion(int)}. - */ - public static class Builder { - private final File mApkFile; - private final DataSource mApkDataSource; - private File mV4SignatureFile; - - private Integer mMinSdkVersion; - private int mMaxSdkVersion = Integer.MAX_VALUE; - - /** - * Constructs a new {@code Builder} for verifying the provided APK file. - */ - public Builder(File apk) { - if (apk == null) { - throw new NullPointerException("apk == null"); - } - mApkFile = apk; - mApkDataSource = null; - } - - /** - * Constructs a new {@code Builder} for verifying the provided APK. - */ - public Builder(DataSource apk) { - if (apk == null) { - throw new NullPointerException("apk == null"); - } - mApkDataSource = apk; - mApkFile = null; - } - - /** - * Sets the oldest Android platform version for which the APK is verified. APK verification - * will confirm that the APK is expected to install successfully on all known Android - * platforms starting from the platform version with the provided API Level. The upper end - * of the platform versions range can be modified via - * {@link #setMaxCheckedPlatformVersion(int)}. - * - *

This method is useful for overriding the default behavior which checks that the APK - * will verify on all platform versions supported by the APK, as specified by - * {@code android:minSdkVersion} attributes in the APK's {@code AndroidManifest.xml}. - * - * @param minSdkVersion API Level of the oldest platform for which to verify the APK - * @see #setMinCheckedPlatformVersion(int) - */ - public Builder setMinCheckedPlatformVersion(int minSdkVersion) { - mMinSdkVersion = minSdkVersion; - return this; - } - - /** - * Sets the newest Android platform version for which the APK is verified. APK verification - * will confirm that the APK is expected to install successfully on all platform versions - * supported by the APK up until and including the provided version. The lower end - * of the platform versions range can be modified via - * {@link #setMinCheckedPlatformVersion(int)}. - * - * @param maxSdkVersion API Level of the newest platform for which to verify the APK - * @see #setMinCheckedPlatformVersion(int) - */ - public Builder setMaxCheckedPlatformVersion(int maxSdkVersion) { - mMaxSdkVersion = maxSdkVersion; - return this; - } - - public Builder setV4SignatureFile(File v4SignatureFile) { - mV4SignatureFile = v4SignatureFile; - return this; - } - - /** - * Returns an {@link ApkVerifier} initialized according to the configuration of this - * builder. - */ - public ApkVerifier build() { - return new ApkVerifier( - mApkFile, - mApkDataSource, - mV4SignatureFile, - mMinSdkVersion, - mMaxSdkVersion); - } - } - - /** - * Adapter for converting base {@link ApkVerificationIssue} instances to their {@link - * IssueWithParams} equivalent. - */ - public static class ApkVerificationIssueAdapter { - private ApkVerificationIssueAdapter() { - } - - // This field is visible for testing - static final Map sVerificationIssueIdToIssue = new HashMap<>(); - - static { - sVerificationIssueIdToIssue.put(ApkVerificationIssue.V2_SIG_MALFORMED_SIGNERS, - Issue.V2_SIG_MALFORMED_SIGNERS); - sVerificationIssueIdToIssue.put(ApkVerificationIssue.V2_SIG_NO_SIGNERS, - Issue.V2_SIG_NO_SIGNERS); - sVerificationIssueIdToIssue.put(ApkVerificationIssue.V2_SIG_MALFORMED_SIGNER, - Issue.V2_SIG_MALFORMED_SIGNER); - sVerificationIssueIdToIssue.put(ApkVerificationIssue.V2_SIG_MALFORMED_SIGNATURE, - Issue.V2_SIG_MALFORMED_SIGNATURE); - sVerificationIssueIdToIssue.put(ApkVerificationIssue.V2_SIG_NO_SIGNATURES, - Issue.V2_SIG_NO_SIGNATURES); - sVerificationIssueIdToIssue.put(ApkVerificationIssue.V2_SIG_MALFORMED_CERTIFICATE, - Issue.V2_SIG_MALFORMED_CERTIFICATE); - sVerificationIssueIdToIssue.put(ApkVerificationIssue.V2_SIG_NO_CERTIFICATES, - Issue.V2_SIG_NO_CERTIFICATES); - sVerificationIssueIdToIssue.put(ApkVerificationIssue.V2_SIG_MALFORMED_DIGEST, - Issue.V2_SIG_MALFORMED_DIGEST); - sVerificationIssueIdToIssue.put(ApkVerificationIssue.V3_SIG_MALFORMED_SIGNERS, - Issue.V3_SIG_MALFORMED_SIGNERS); - sVerificationIssueIdToIssue.put(ApkVerificationIssue.V3_SIG_NO_SIGNERS, - Issue.V3_SIG_NO_SIGNERS); - sVerificationIssueIdToIssue.put(ApkVerificationIssue.V3_SIG_MALFORMED_SIGNER, - Issue.V3_SIG_MALFORMED_SIGNER); - sVerificationIssueIdToIssue.put(ApkVerificationIssue.V3_SIG_MALFORMED_SIGNATURE, - Issue.V3_SIG_MALFORMED_SIGNATURE); - sVerificationIssueIdToIssue.put(ApkVerificationIssue.V3_SIG_NO_SIGNATURES, - Issue.V3_SIG_NO_SIGNATURES); - sVerificationIssueIdToIssue.put(ApkVerificationIssue.V3_SIG_MALFORMED_CERTIFICATE, - Issue.V3_SIG_MALFORMED_CERTIFICATE); - sVerificationIssueIdToIssue.put(ApkVerificationIssue.V3_SIG_NO_CERTIFICATES, - Issue.V3_SIG_NO_CERTIFICATES); - sVerificationIssueIdToIssue.put(ApkVerificationIssue.V3_SIG_MALFORMED_DIGEST, - Issue.V3_SIG_MALFORMED_DIGEST); - sVerificationIssueIdToIssue.put(ApkVerificationIssue.SOURCE_STAMP_NO_SIGNATURE, - Issue.SOURCE_STAMP_NO_SIGNATURE); - sVerificationIssueIdToIssue.put(ApkVerificationIssue.SOURCE_STAMP_MALFORMED_CERTIFICATE, - Issue.SOURCE_STAMP_MALFORMED_CERTIFICATE); - sVerificationIssueIdToIssue.put(ApkVerificationIssue.SOURCE_STAMP_UNKNOWN_SIG_ALGORITHM, - Issue.SOURCE_STAMP_UNKNOWN_SIG_ALGORITHM); - sVerificationIssueIdToIssue.put(ApkVerificationIssue.SOURCE_STAMP_MALFORMED_SIGNATURE, - Issue.SOURCE_STAMP_MALFORMED_SIGNATURE); - sVerificationIssueIdToIssue.put(ApkVerificationIssue.SOURCE_STAMP_DID_NOT_VERIFY, - Issue.SOURCE_STAMP_DID_NOT_VERIFY); - sVerificationIssueIdToIssue.put(ApkVerificationIssue.SOURCE_STAMP_VERIFY_EXCEPTION, - Issue.SOURCE_STAMP_VERIFY_EXCEPTION); - sVerificationIssueIdToIssue.put( - ApkVerificationIssue.SOURCE_STAMP_EXPECTED_DIGEST_MISMATCH, - Issue.SOURCE_STAMP_EXPECTED_DIGEST_MISMATCH); - sVerificationIssueIdToIssue.put( - ApkVerificationIssue.SOURCE_STAMP_SIGNATURE_BLOCK_WITHOUT_CERT_DIGEST, - Issue.SOURCE_STAMP_SIGNATURE_BLOCK_WITHOUT_CERT_DIGEST); - sVerificationIssueIdToIssue.put( - ApkVerificationIssue.SOURCE_STAMP_CERT_DIGEST_AND_SIG_BLOCK_MISSING, - Issue.SOURCE_STAMP_CERT_DIGEST_AND_SIG_BLOCK_MISSING); - sVerificationIssueIdToIssue.put( - ApkVerificationIssue.SOURCE_STAMP_NO_SUPPORTED_SIGNATURE, - Issue.SOURCE_STAMP_NO_SUPPORTED_SIGNATURE); - sVerificationIssueIdToIssue.put( - ApkVerificationIssue - .SOURCE_STAMP_CERTIFICATE_MISMATCH_BETWEEN_SIGNATURE_BLOCK_AND_APK, - Issue.SOURCE_STAMP_CERTIFICATE_MISMATCH_BETWEEN_SIGNATURE_BLOCK_AND_APK); - sVerificationIssueIdToIssue.put(ApkVerificationIssue.MALFORMED_APK, - Issue.MALFORMED_APK); - sVerificationIssueIdToIssue.put(ApkVerificationIssue.UNEXPECTED_EXCEPTION, - Issue.UNEXPECTED_EXCEPTION); - sVerificationIssueIdToIssue.put(ApkVerificationIssue.SOURCE_STAMP_SIG_MISSING, - Issue.SOURCE_STAMP_SIG_MISSING); - sVerificationIssueIdToIssue.put(ApkVerificationIssue.SOURCE_STAMP_MALFORMED_ATTRIBUTE, - Issue.SOURCE_STAMP_MALFORMED_ATTRIBUTE); - sVerificationIssueIdToIssue.put(ApkVerificationIssue.SOURCE_STAMP_UNKNOWN_ATTRIBUTE, - Issue.SOURCE_STAMP_UNKNOWN_ATTRIBUTE); - sVerificationIssueIdToIssue.put(ApkVerificationIssue.SOURCE_STAMP_MALFORMED_LINEAGE, - Issue.SOURCE_STAMP_MALFORMED_LINEAGE); - sVerificationIssueIdToIssue.put(ApkVerificationIssue.SOURCE_STAMP_POR_CERT_MISMATCH, - Issue.SOURCE_STAMP_POR_CERT_MISMATCH); - sVerificationIssueIdToIssue.put(ApkVerificationIssue.SOURCE_STAMP_POR_DID_NOT_VERIFY, - Issue.SOURCE_STAMP_POR_DID_NOT_VERIFY); - sVerificationIssueIdToIssue.put(ApkVerificationIssue.JAR_SIG_NO_SIGNATURES, - Issue.JAR_SIG_NO_SIGNATURES); - sVerificationIssueIdToIssue.put(ApkVerificationIssue.JAR_SIG_PARSE_EXCEPTION, - Issue.JAR_SIG_PARSE_EXCEPTION); - } - - /** - * Converts the provided {@code verificationIssues} to a {@code List} of corresponding - * {@link IssueWithParams} instances. - */ - public static List getIssuesFromVerificationIssues( - List verificationIssues) { - List result = new ArrayList<>(verificationIssues.size()); - for (ApkVerificationIssue issue : verificationIssues) { - if (issue instanceof IssueWithParams) { - result.add((IssueWithParams) issue); - } else { - result.add( - new IssueWithParams(sVerificationIssueIdToIssue.get(issue.getIssueId()), - issue.getParams())); - } - } - return result; - } - } -} diff --git a/app/src/main/java/com/android/apksig/Constants.java b/app/src/main/java/com/android/apksig/Constants.java deleted file mode 100644 index 32c7375c09..0000000000 --- a/app/src/main/java/com/android/apksig/Constants.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig; - -import com.android.apksig.internal.apk.stamp.SourceStampConstants; -import com.android.apksig.internal.apk.v1.V1SchemeConstants; -import com.android.apksig.internal.apk.v2.V2SchemeConstants; -import com.android.apksig.internal.apk.v3.V3SchemeConstants; - -/** - * Exports internally defined constants to allow clients to reference these values without relying - * on internal code. - */ -public class Constants { - private Constants() {} - - public static final int VERSION_SOURCE_STAMP = 0; - public static final int VERSION_JAR_SIGNATURE_SCHEME = 1; - public static final int VERSION_APK_SIGNATURE_SCHEME_V2 = 2; - public static final int VERSION_APK_SIGNATURE_SCHEME_V3 = 3; - public static final int VERSION_APK_SIGNATURE_SCHEME_V4 = 4; - - public static final String MANIFEST_ENTRY_NAME = V1SchemeConstants.MANIFEST_ENTRY_NAME; - - public static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = - V2SchemeConstants.APK_SIGNATURE_SCHEME_V2_BLOCK_ID; - - public static final int APK_SIGNATURE_SCHEME_V3_BLOCK_ID = - V3SchemeConstants.APK_SIGNATURE_SCHEME_V3_BLOCK_ID; - public static final int PROOF_OF_ROTATION_ATTR_ID = V3SchemeConstants.PROOF_OF_ROTATION_ATTR_ID; - - public static final int V1_SOURCE_STAMP_BLOCK_ID = - SourceStampConstants.V1_SOURCE_STAMP_BLOCK_ID; - public static final int V2_SOURCE_STAMP_BLOCK_ID = - SourceStampConstants.V2_SOURCE_STAMP_BLOCK_ID; - - public static final String OID_RSA_ENCRYPTION = "1.2.840.113549.1.1.1"; -} diff --git a/app/src/main/java/com/android/apksig/DefaultApkSignerEngine.java b/app/src/main/java/com/android/apksig/DefaultApkSignerEngine.java deleted file mode 100644 index e2256dadb9..0000000000 --- a/app/src/main/java/com/android/apksig/DefaultApkSignerEngine.java +++ /dev/null @@ -1,1844 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig; - -import static com.android.apksig.apk.ApkUtils.SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME; -import static com.android.apksig.apk.ApkUtils.computeSha256DigestBytes; -import static com.android.apksig.internal.apk.ApkSigningBlockUtils.VERITY_PADDING_BLOCK_ID; -import static com.android.apksig.internal.apk.ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2; -import static com.android.apksig.internal.apk.ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3; -import static com.android.apksig.internal.apk.ApkSigningBlockUtils.VERSION_JAR_SIGNATURE_SCHEME; - -import com.android.apksig.apk.ApkFormatException; -import com.android.apksig.apk.ApkUtils; -import com.android.apksig.internal.apk.ApkSigningBlockUtils; -import com.android.apksig.internal.apk.ContentDigestAlgorithm; -import com.android.apksig.internal.apk.SignatureAlgorithm; -import com.android.apksig.internal.apk.stamp.V2SourceStampSigner; -import com.android.apksig.internal.apk.v1.DigestAlgorithm; -import com.android.apksig.internal.apk.v1.V1SchemeConstants; -import com.android.apksig.internal.apk.v1.V1SchemeSigner; -import com.android.apksig.internal.apk.v1.V1SchemeVerifier; -import com.android.apksig.internal.apk.v2.V2SchemeSigner; -import com.android.apksig.internal.apk.v3.V3SchemeSigner; -import com.android.apksig.internal.apk.v4.V4SchemeSigner; -import com.android.apksig.internal.apk.v4.V4Signature; -import com.android.apksig.internal.jar.ManifestParser; -import com.android.apksig.internal.util.AndroidSdkVersion; -import com.android.apksig.internal.util.Pair; -import com.android.apksig.internal.util.TeeDataSink; -import com.android.apksig.util.DataSink; -import com.android.apksig.util.DataSinks; -import com.android.apksig.util.DataSource; -import com.android.apksig.util.RunnablesExecutor; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.security.InvalidKeyException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.SignatureException; -import java.security.cert.CertificateEncodingException; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * Default implementation of {@link ApkSignerEngine}. - * - *

Use {@link Builder} to obtain instances of this engine. - */ -public class DefaultApkSignerEngine implements ApkSignerEngine { - - // IMPLEMENTATION NOTE: This engine generates a signed APK as follows: - // 1. The engine asks its client to output input JAR entries which are not part of JAR - // signature. - // 2. If JAR signing (v1 signing) is enabled, the engine inspects the output JAR entries to - // compute their digests, to be placed into output META-INF/MANIFEST.MF. It also inspects - // the contents of input and output META-INF/MANIFEST.MF to borrow the main section of the - // file. It does not care about individual (i.e., JAR entry-specific) sections. It then - // emits the v1 signature (a set of JAR entries) and asks the client to output them. - // 3. If APK Signature Scheme v2 (v2 signing) is enabled, the engine emits an APK Signing Block - // from outputZipSections() and asks its client to insert this block into the output. - // 4. If APK Signature Scheme v3 (v3 signing) is enabled, the engine includes it in the APK - // Signing BLock output from outputZipSections() and asks its client to insert this block - // into the output. If both v2 and v3 signing is enabled, they are both added to the APK - // Signing Block before asking the client to insert it into the output. - - private final boolean mV1SigningEnabled; - private final boolean mV2SigningEnabled; - private final boolean mV3SigningEnabled; - private final boolean mVerityEnabled; - private final boolean mDebuggableApkPermitted; - private final boolean mOtherSignersSignaturesPreserved; - private final String mCreatedBy; - private final List mSignerConfigs; - private final SignerConfig mSourceStampSignerConfig; - private final SigningCertificateLineage mSourceStampSigningCertificateLineage; - private final int mMinSdkVersion; - private final SigningCertificateLineage mSigningCertificateLineage; - - private List mPreservedV2Signers = Collections.emptyList(); - private List> mPreservedSignatureBlocks = Collections.emptyList(); - - private List mV1SignerConfigs = Collections.emptyList(); - private DigestAlgorithm mV1ContentDigestAlgorithm; - - private boolean mClosed; - - private boolean mV1SignaturePending; - - /** Names of JAR entries which this engine is expected to output as part of v1 signing. */ - private Set mSignatureExpectedOutputJarEntryNames = Collections.emptySet(); - - /** Requests for digests of output JAR entries. */ - private final Map mOutputJarEntryDigestRequests = - new HashMap<>(); - - /** Digests of output JAR entries. */ - private final Map mOutputJarEntryDigests = new HashMap<>(); - - /** Data of JAR entries emitted by this engine as v1 signature. */ - private final Map mEmittedSignatureJarEntryData = new HashMap<>(); - - /** Requests for data of output JAR entries which comprise the v1 signature. */ - private final Map mOutputSignatureJarEntryDataRequests = - new HashMap<>(); - /** - * Request to obtain the data of MANIFEST.MF or {@code null} if the request hasn't been issued. - */ - private GetJarEntryDataRequest mInputJarManifestEntryDataRequest; - - /** - * Request to obtain the data of AndroidManifest.xml or {@code null} if the request hasn't been - * issued. - */ - private GetJarEntryDataRequest mOutputAndroidManifestEntryDataRequest; - - /** - * Whether the package being signed is marked as {@code android:debuggable} or {@code null} if - * this is not yet known. - */ - private Boolean mDebuggable; - - /** - * Request to output the emitted v1 signature or {@code null} if the request hasn't been issued. - */ - private OutputJarSignatureRequestImpl mAddV1SignatureRequest; - - private boolean mV2SignaturePending; - private boolean mV3SignaturePending; - - /** - * Request to output the emitted v2 and/or v3 signature(s) {@code null} if the request hasn't - * been issued. - */ - private OutputApkSigningBlockRequestImpl mAddSigningBlockRequest; - - private RunnablesExecutor mExecutor = RunnablesExecutor.MULTI_THREADED; - - /** - * A Set of block IDs to be discarded when requesting to preserve the original signatures. - */ - private static final Set DISCARDED_SIGNATURE_BLOCK_IDS; - static { - DISCARDED_SIGNATURE_BLOCK_IDS = new HashSet<>(3); - // The verity padding block is recomputed on an - // ApkSigningBlockUtils.ANDROID_COMMON_PAGE_ALIGNMENT_BYTES boundary. - DISCARDED_SIGNATURE_BLOCK_IDS.add(VERITY_PADDING_BLOCK_ID); - // The source stamp block is not currently preserved; appending a new signature scheme - // block will invalidate the previous source stamp. - DISCARDED_SIGNATURE_BLOCK_IDS.add(Constants.V1_SOURCE_STAMP_BLOCK_ID); - DISCARDED_SIGNATURE_BLOCK_IDS.add(Constants.V2_SOURCE_STAMP_BLOCK_ID); - } - - private DefaultApkSignerEngine( - List signerConfigs, - SignerConfig sourceStampSignerConfig, - SigningCertificateLineage sourceStampSigningCertificateLineage, - int minSdkVersion, - boolean v1SigningEnabled, - boolean v2SigningEnabled, - boolean v3SigningEnabled, - boolean verityEnabled, - boolean debuggableApkPermitted, - boolean otherSignersSignaturesPreserved, - String createdBy, - SigningCertificateLineage signingCertificateLineage) - throws InvalidKeyException { - if (signerConfigs.isEmpty()) { - throw new IllegalArgumentException("At least one signer config must be provided"); - } - - mV1SigningEnabled = v1SigningEnabled; - mV2SigningEnabled = v2SigningEnabled; - mV3SigningEnabled = v3SigningEnabled; - mVerityEnabled = verityEnabled; - mV1SignaturePending = v1SigningEnabled; - mV2SignaturePending = v2SigningEnabled; - mV3SignaturePending = v3SigningEnabled; - mDebuggableApkPermitted = debuggableApkPermitted; - mOtherSignersSignaturesPreserved = otherSignersSignaturesPreserved; - mCreatedBy = createdBy; - mSignerConfigs = signerConfigs; - mSourceStampSignerConfig = sourceStampSignerConfig; - mSourceStampSigningCertificateLineage = sourceStampSigningCertificateLineage; - mMinSdkVersion = minSdkVersion; - mSigningCertificateLineage = signingCertificateLineage; - - if (v1SigningEnabled) { - if (v3SigningEnabled) { - - // v3 signing only supports single signers, of which the oldest (first) will be the - // one to use for v1 and v2 signing - SignerConfig oldestConfig = signerConfigs.get(0); - - // in the event of signing certificate changes, make sure we have the oldest in the - // signing history to sign with v1 - if (signingCertificateLineage != null) { - SigningCertificateLineage subLineage = - signingCertificateLineage.getSubLineage( - oldestConfig.mCertificates.get(0)); - if (subLineage.size() != 1) { - throw new IllegalArgumentException( - "v1 signing enabled but the oldest signer in the" - + " SigningCertificateLineage is missing. Please provide the" - + " oldest signer to enable v1 signing"); - } - } - createV1SignerConfigs(Collections.singletonList(oldestConfig), minSdkVersion); - } else { - createV1SignerConfigs(signerConfigs, minSdkVersion); - } - } - } - - private void createV1SignerConfigs(List signerConfigs, int minSdkVersion) - throws InvalidKeyException { - mV1SignerConfigs = new ArrayList<>(signerConfigs.size()); - Map v1SignerNameToSignerIndex = new HashMap<>(signerConfigs.size()); - DigestAlgorithm v1ContentDigestAlgorithm = null; - for (int i = 0; i < signerConfigs.size(); i++) { - SignerConfig signerConfig = signerConfigs.get(i); - List certificates = signerConfig.getCertificates(); - PublicKey publicKey = certificates.get(0).getPublicKey(); - - String v1SignerName = V1SchemeSigner.getSafeSignerName(signerConfig.getName()); - // Check whether the signer's name is unique among all v1 signers - Integer indexOfOtherSignerWithSameName = v1SignerNameToSignerIndex.put(v1SignerName, i); - if (indexOfOtherSignerWithSameName != null) { - throw new IllegalArgumentException( - "Signers #" - + (indexOfOtherSignerWithSameName + 1) - + " and #" - + (i + 1) - + " have the same name: " - + v1SignerName - + ". v1 signer names must be unique"); - } - - DigestAlgorithm v1SignatureDigestAlgorithm = - V1SchemeSigner.getSuggestedSignatureDigestAlgorithm(publicKey, minSdkVersion); - V1SchemeSigner.SignerConfig v1SignerConfig = new V1SchemeSigner.SignerConfig(); - v1SignerConfig.name = v1SignerName; - v1SignerConfig.privateKey = signerConfig.getPrivateKey(); - v1SignerConfig.certificates = certificates; - v1SignerConfig.signatureDigestAlgorithm = v1SignatureDigestAlgorithm; - v1SignerConfig.deterministicDsaSigning = signerConfig.getDeterministicDsaSigning(); - // For digesting contents of APK entries and of MANIFEST.MF, pick the algorithm - // of comparable strength to the digest algorithm used for computing the signature. - // When there are multiple signers, pick the strongest digest algorithm out of their - // signature digest algorithms. This avoids reducing the digest strength used by any - // of the signers to protect APK contents. - if (v1ContentDigestAlgorithm == null) { - v1ContentDigestAlgorithm = v1SignatureDigestAlgorithm; - } else { - if (DigestAlgorithm.BY_STRENGTH_COMPARATOR.compare( - v1SignatureDigestAlgorithm, v1ContentDigestAlgorithm) - > 0) { - v1ContentDigestAlgorithm = v1SignatureDigestAlgorithm; - } - } - mV1SignerConfigs.add(v1SignerConfig); - } - mV1ContentDigestAlgorithm = v1ContentDigestAlgorithm; - mSignatureExpectedOutputJarEntryNames = - V1SchemeSigner.getOutputEntryNames(mV1SignerConfigs); - } - - private List createV2SignerConfigs( - boolean apkSigningBlockPaddingSupported) throws InvalidKeyException { - if (mV3SigningEnabled) { - - // v3 signing only supports single signers, of which the oldest (first) will be the one - // to use for v1 and v2 signing - List signerConfig = new ArrayList<>(); - - SignerConfig oldestConfig = mSignerConfigs.get(0); - - // first make sure that if we have signing certificate history that the oldest signer - // corresponds to the oldest ancestor - if (mSigningCertificateLineage != null) { - SigningCertificateLineage subLineage = - mSigningCertificateLineage.getSubLineage(oldestConfig.mCertificates.get(0)); - if (subLineage.size() != 1) { - throw new IllegalArgumentException( - "v2 signing enabled but the oldest signer in" - + " the SigningCertificateLineage is missing. Please provide" - + " the oldest signer to enable v2 signing."); - } - } - signerConfig.add( - createSigningBlockSignerConfig( - mSignerConfigs.get(0), - apkSigningBlockPaddingSupported, - ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2)); - return signerConfig; - } else { - return createSigningBlockSignerConfigs( - apkSigningBlockPaddingSupported, - ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2); - } - } - - private List processV3Configs( - List rawConfigs) throws InvalidKeyException { - List processedConfigs = new ArrayList<>(); - - // we have our configs, now touch them up to appropriately cover all SDK levels since APK - // signature scheme v3 was introduced - int currentMinSdk = Integer.MAX_VALUE; - for (int i = rawConfigs.size() - 1; i >= 0; i--) { - ApkSigningBlockUtils.SignerConfig config = rawConfigs.get(i); - if (config.signatureAlgorithms == null) { - // no valid algorithm was found for this signer, and we haven't yet covered all - // platform versions, something's wrong - String keyAlgorithm = config.certificates.get(0).getPublicKey().getAlgorithm(); - throw new InvalidKeyException( - "Unsupported key algorithm " - + keyAlgorithm - + " is " - + "not supported for APK Signature Scheme v3 signing"); - } - if (i == rawConfigs.size() - 1) { - // first go through the loop, config should support all future platform versions. - // this assumes we don't deprecate support for signers in the future. If we do, - // this needs to change - config.maxSdkVersion = Integer.MAX_VALUE; - } else { - // otherwise, we only want to use this signer up to the minimum platform version - // on which a newer one is acceptable - config.maxSdkVersion = currentMinSdk - 1; - } - config.minSdkVersion = getMinSdkFromV3SignatureAlgorithms(config.signatureAlgorithms); - if (mSigningCertificateLineage != null) { - config.mSigningCertificateLineage = - mSigningCertificateLineage.getSubLineage(config.certificates.get(0)); - } - // we know that this config will be used, so add it to our result, order doesn't matter - // at this point (and likely only one will be needed - processedConfigs.add(config); - currentMinSdk = config.minSdkVersion; - if (currentMinSdk <= mMinSdkVersion || currentMinSdk <= AndroidSdkVersion.P) { - // this satisfies all we need, stop here - break; - } - } - if (currentMinSdk > AndroidSdkVersion.P && currentMinSdk > mMinSdkVersion) { - // we can't cover all desired SDK versions, abort - throw new InvalidKeyException( - "Provided key algorithms not supported on all desired " - + "Android SDK versions"); - } - - return processedConfigs; - } - - private List createV3SignerConfigs( - boolean apkSigningBlockPaddingSupported) throws InvalidKeyException { - return processV3Configs(createSigningBlockSignerConfigs(apkSigningBlockPaddingSupported, - ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3)); - } - - private ApkSigningBlockUtils.SignerConfig createV4SignerConfig() throws InvalidKeyException { - List configs = createSigningBlockSignerConfigs(true, - ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V4); - if (configs.size() != 1) { - // V4 only uses signer config to connect back to v3. Use the same filtering logic. - configs = processV3Configs(configs); - } - if (configs.size() != 1) { - throw new InvalidKeyException("Only accepting one signer config for V4 Signature."); - } - return configs.get(0); - } - - private ApkSigningBlockUtils.SignerConfig createSourceStampSignerConfig() - throws InvalidKeyException { - ApkSigningBlockUtils.SignerConfig config = createSigningBlockSignerConfig( - mSourceStampSignerConfig, - /* apkSigningBlockPaddingSupported= */ false, - ApkSigningBlockUtils.VERSION_SOURCE_STAMP); - if (mSourceStampSigningCertificateLineage != null) { - config.mSigningCertificateLineage = mSourceStampSigningCertificateLineage.getSubLineage( - config.certificates.get(0)); - } - return config; - } - - private int getMinSdkFromV3SignatureAlgorithms(List algorithms) { - int min = Integer.MAX_VALUE; - for (SignatureAlgorithm algorithm : algorithms) { - int current = algorithm.getMinSdkVersion(); - if (current < min) { - if (current <= mMinSdkVersion || current <= AndroidSdkVersion.P) { - // this algorithm satisfies all of our needs, no need to keep looking - return current; - } else { - min = current; - } - } - } - return min; - } - - private List createSigningBlockSignerConfigs( - boolean apkSigningBlockPaddingSupported, int schemeId) throws InvalidKeyException { - List signerConfigs = - new ArrayList<>(mSignerConfigs.size()); - for (int i = 0; i < mSignerConfigs.size(); i++) { - SignerConfig signerConfig = mSignerConfigs.get(i); - signerConfigs.add( - createSigningBlockSignerConfig( - signerConfig, apkSigningBlockPaddingSupported, schemeId)); - } - return signerConfigs; - } - - private ApkSigningBlockUtils.SignerConfig createSigningBlockSignerConfig( - SignerConfig signerConfig, boolean apkSigningBlockPaddingSupported, int schemeId) - throws InvalidKeyException { - List certificates = signerConfig.getCertificates(); - PublicKey publicKey = certificates.get(0).getPublicKey(); - - ApkSigningBlockUtils.SignerConfig newSignerConfig = new ApkSigningBlockUtils.SignerConfig(); - newSignerConfig.privateKey = signerConfig.getPrivateKey(); - newSignerConfig.certificates = certificates; - - switch (schemeId) { - case ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2: - newSignerConfig.signatureAlgorithms = - V2SchemeSigner.getSuggestedSignatureAlgorithms( - publicKey, - mMinSdkVersion, - apkSigningBlockPaddingSupported && mVerityEnabled, - signerConfig.getDeterministicDsaSigning()); - break; - case ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3: - try { - newSignerConfig.signatureAlgorithms = - V3SchemeSigner.getSuggestedSignatureAlgorithms( - publicKey, - mMinSdkVersion, - apkSigningBlockPaddingSupported && mVerityEnabled, - signerConfig.getDeterministicDsaSigning()); - } catch (InvalidKeyException e) { - - // It is possible for a signer used for v1/v2 signing to not be allowed for use - // with v3 signing. This is ok as long as there exists a more recent v3 signer - // that covers all supported platform versions. Populate signatureAlgorithm - // with null, it will be cleaned-up in a later step. - newSignerConfig.signatureAlgorithms = null; - } - break; - case ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V4: - try { - newSignerConfig.signatureAlgorithms = - V4SchemeSigner.getSuggestedSignatureAlgorithms( - publicKey, mMinSdkVersion, apkSigningBlockPaddingSupported, - signerConfig.getDeterministicDsaSigning()); - } catch (InvalidKeyException e) { - // V4 is an optional signing schema, ok to proceed without. - newSignerConfig.signatureAlgorithms = null; - } - break; - case ApkSigningBlockUtils.VERSION_SOURCE_STAMP: - newSignerConfig.signatureAlgorithms = - Collections.singletonList( - SignatureAlgorithm.RSA_PKCS1_V1_5_WITH_SHA256); - break; - default: - throw new IllegalArgumentException("Unknown APK Signature Scheme ID requested"); - } - return newSignerConfig; - } - - private boolean isDebuggable(String entryName) { - return mDebuggableApkPermitted - || !ApkUtils.ANDROID_MANIFEST_ZIP_ENTRY_NAME.equals(entryName); - } - - /** - * Initializes DefaultApkSignerEngine with the existing MANIFEST.MF. This reads existing digests - * from the MANIFEST.MF file (they are assumed correct) and stores them for the final signature - * without recalculation. This step has a significant performance benefit in case of incremental - * build. - * - *

This method extracts and stored computed digest for every entry that it would compute it - * for in the {@link #outputJarEntry(String)} method - * - * @param manifestBytes raw representation of MANIFEST.MF file - * @param entryNames a set of expected entries names - * @return set of entry names which were processed by the engine during the initialization, a - * subset of entryNames - */ - @Override - @SuppressWarnings("AndroidJdkLibsChecker") - public Set initWith(byte[] manifestBytes, Set entryNames) { - V1SchemeVerifier.Result result = new V1SchemeVerifier.Result(); - Pair> sections = - V1SchemeVerifier.parseManifest(manifestBytes, entryNames, result); - String alg = V1SchemeSigner.getJcaMessageDigestAlgorithm(mV1ContentDigestAlgorithm); - for (Map.Entry entry : sections.getSecond().entrySet()) { - String entryName = entry.getKey(); - if (V1SchemeSigner.isJarEntryDigestNeededInManifest(entry.getKey()) - && isDebuggable(entryName)) { - - V1SchemeVerifier.NamedDigest extractedDigest = null; - Collection digestsToVerify = - V1SchemeVerifier.getDigestsToVerify( - entry.getValue(), "-Digest", mMinSdkVersion, Integer.MAX_VALUE); - for (V1SchemeVerifier.NamedDigest digestToVerify : digestsToVerify) { - if (digestToVerify.jcaDigestAlgorithm.equals(alg)) { - extractedDigest = digestToVerify; - break; - } - } - if (extractedDigest != null) { - mOutputJarEntryDigests.put(entryName, extractedDigest.digest); - } - } - } - return mOutputJarEntryDigests.keySet(); - } - - @Override - public void setExecutor(RunnablesExecutor executor) { - mExecutor = executor; - } - - @Override - public void inputApkSigningBlock(DataSource apkSigningBlock) { - checkNotClosed(); - - if ((apkSigningBlock == null) || (apkSigningBlock.size() == 0)) { - return; - } - - if (mOtherSignersSignaturesPreserved) { - boolean schemeSignatureBlockPreserved = false; - mPreservedSignatureBlocks = new ArrayList<>(); - try { - List> signatureBlocks = - ApkSigningBlockUtils.getApkSignatureBlocks(apkSigningBlock); - for (Pair signatureBlock : signatureBlocks) { - if (signatureBlock.getSecond() == Constants.APK_SIGNATURE_SCHEME_V2_BLOCK_ID) { - // If a V2 signature block is found and the engine is configured to use V2 - // then save any of the previous signers that are not part of the current - // signing request. - if (mV2SigningEnabled) { - List, byte[]>> v2Signers = - ApkSigningBlockUtils.getApkSignatureBlockSigners( - signatureBlock.getFirst()); - mPreservedV2Signers = new ArrayList<>(v2Signers.size()); - for (Pair, byte[]> v2Signer : v2Signers) { - if (!isConfiguredWithSigner(v2Signer.getFirst())) { - mPreservedV2Signers.add(v2Signer.getSecond()); - schemeSignatureBlockPreserved = true; - } - } - } else { - // else V2 signing is not enabled; save the entire signature block to be - // added to the final APK signing block. - mPreservedSignatureBlocks.add(signatureBlock); - schemeSignatureBlockPreserved = true; - } - } else if (signatureBlock.getSecond() - == Constants.APK_SIGNATURE_SCHEME_V3_BLOCK_ID) { - // Preserving other signers in the presence of a V3 signature block is only - // supported if the engine is configured to resign the APK with the V3 - // signature scheme, and the V3 signer in the signature block is the same - // as the engine is configured to use. - if (!mV3SigningEnabled) { - throw new IllegalStateException( - "Preserving an existing V3 signature is not supported"); - } - List, byte[]>> v3Signers = - ApkSigningBlockUtils.getApkSignatureBlockSigners( - signatureBlock.getFirst()); - if (v3Signers.size() > 1) { - throw new IllegalArgumentException( - "The provided APK signing block contains " + v3Signers.size() - + " V3 signers; the V3 signature scheme only supports" - + " one signer"); - } - // If there is only a single V3 signer then ensure it is the signer - // configured to sign the APK. - if (v3Signers.size() == 1 - && !isConfiguredWithSigner(v3Signers.get(0).getFirst())) { - throw new IllegalStateException( - "The V3 signature scheme only supports one signer; a request " - + "was made to preserve the existing V3 signature, " - + "but the engine is configured to sign with a " - + "different signer"); - } - } else if (!DISCARDED_SIGNATURE_BLOCK_IDS.contains( - signatureBlock.getSecond())) { - mPreservedSignatureBlocks.add(signatureBlock); - } - } - } catch (ApkFormatException | CertificateException | IOException e) { - throw new IllegalArgumentException("Unable to parse the provided signing block", e); - } - // Signature scheme V3+ only support a single signer; if the engine is configured to - // sign with V3+ then ensure no scheme signature blocks have been preserved. - if (mV3SigningEnabled && schemeSignatureBlockPreserved) { - throw new IllegalStateException( - "Signature scheme V3+ only supports a single signer and cannot be " - + "appended to the existing signature scheme blocks"); - } - return; - } - } - - /** - * Returns whether the engine is configured to sign the APK with a signer using the specified - * {@code signerCerts}. - */ - private boolean isConfiguredWithSigner(List signerCerts) { - for (SignerConfig signerConfig : mSignerConfigs) { - if (signerCerts.containsAll(signerConfig.getCertificates())) { - return true; - } - } - return false; - } - - @Override - public InputJarEntryInstructions inputJarEntry(String entryName) { - checkNotClosed(); - - InputJarEntryInstructions.OutputPolicy outputPolicy = - getInputJarEntryOutputPolicy(entryName); - switch (outputPolicy) { - case SKIP: - return new InputJarEntryInstructions(InputJarEntryInstructions.OutputPolicy.SKIP); - case OUTPUT: - return new InputJarEntryInstructions(InputJarEntryInstructions.OutputPolicy.OUTPUT); - case OUTPUT_BY_ENGINE: - if (V1SchemeConstants.MANIFEST_ENTRY_NAME.equals(entryName)) { - // We copy the main section of the JAR manifest from input to output. Thus, this - // invalidates v1 signature and we need to see the entry's data. - mInputJarManifestEntryDataRequest = new GetJarEntryDataRequest(entryName); - return new InputJarEntryInstructions( - InputJarEntryInstructions.OutputPolicy.OUTPUT_BY_ENGINE, - mInputJarManifestEntryDataRequest); - } - return new InputJarEntryInstructions( - InputJarEntryInstructions.OutputPolicy.OUTPUT_BY_ENGINE); - default: - throw new RuntimeException("Unsupported output policy: " + outputPolicy); - } - } - - @Override - public InspectJarEntryRequest outputJarEntry(String entryName) { - checkNotClosed(); - invalidateV2Signature(); - - if (!isDebuggable(entryName)) { - forgetOutputApkDebuggableStatus(); - } - - if (!mV1SigningEnabled) { - // No need to inspect JAR entries when v1 signing is not enabled. - if (!isDebuggable(entryName)) { - // To reject debuggable APKs we need to inspect the APK's AndroidManifest.xml to - // check whether it declares that the APK is debuggable - mOutputAndroidManifestEntryDataRequest = new GetJarEntryDataRequest(entryName); - return mOutputAndroidManifestEntryDataRequest; - } - return null; - } - // v1 signing is enabled - - if (V1SchemeSigner.isJarEntryDigestNeededInManifest(entryName)) { - // This entry is covered by v1 signature. We thus need to inspect the entry's data to - // compute its digest(s) for v1 signature. - - // TODO: Handle the case where other signer's v1 signatures are present and need to be - // preserved. In that scenario we can't modify MANIFEST.MF and add/remove JAR entries - // covered by v1 signature. - invalidateV1Signature(); - GetJarEntryDataDigestRequest dataDigestRequest = - new GetJarEntryDataDigestRequest( - entryName, - V1SchemeSigner.getJcaMessageDigestAlgorithm(mV1ContentDigestAlgorithm)); - mOutputJarEntryDigestRequests.put(entryName, dataDigestRequest); - mOutputJarEntryDigests.remove(entryName); - - if ((!mDebuggableApkPermitted) - && (ApkUtils.ANDROID_MANIFEST_ZIP_ENTRY_NAME.equals(entryName))) { - // To reject debuggable APKs we need to inspect the APK's AndroidManifest.xml to - // check whether it declares that the APK is debuggable - mOutputAndroidManifestEntryDataRequest = new GetJarEntryDataRequest(entryName); - return new CompoundInspectJarEntryRequest( - entryName, mOutputAndroidManifestEntryDataRequest, dataDigestRequest); - } - - return dataDigestRequest; - } - - if (mSignatureExpectedOutputJarEntryNames.contains(entryName)) { - // This entry is part of v1 signature generated by this engine. We need to check whether - // the entry's data is as output by the engine. - invalidateV1Signature(); - GetJarEntryDataRequest dataRequest; - if (V1SchemeConstants.MANIFEST_ENTRY_NAME.equals(entryName)) { - dataRequest = new GetJarEntryDataRequest(entryName); - mInputJarManifestEntryDataRequest = dataRequest; - } else { - // If this entry is part of v1 signature which has been emitted by this engine, - // check whether the output entry's data matches what the engine emitted. - dataRequest = - (mEmittedSignatureJarEntryData.containsKey(entryName)) - ? new GetJarEntryDataRequest(entryName) - : null; - } - - if (dataRequest != null) { - mOutputSignatureJarEntryDataRequests.put(entryName, dataRequest); - } - return dataRequest; - } - - // This entry is not covered by v1 signature and isn't part of v1 signature. - return null; - } - - @Override - public InputJarEntryInstructions.OutputPolicy inputJarEntryRemoved(String entryName) { - checkNotClosed(); - return getInputJarEntryOutputPolicy(entryName); - } - - @Override - public void outputJarEntryRemoved(String entryName) { - checkNotClosed(); - invalidateV2Signature(); - if (!mV1SigningEnabled) { - return; - } - - if (V1SchemeSigner.isJarEntryDigestNeededInManifest(entryName)) { - // This entry is covered by v1 signature. - invalidateV1Signature(); - mOutputJarEntryDigests.remove(entryName); - mOutputJarEntryDigestRequests.remove(entryName); - mOutputSignatureJarEntryDataRequests.remove(entryName); - return; - } - - if (mSignatureExpectedOutputJarEntryNames.contains(entryName)) { - // This entry is part of the v1 signature generated by this engine. - invalidateV1Signature(); - return; - } - } - - @Override - public OutputJarSignatureRequest outputJarEntries() - throws ApkFormatException, InvalidKeyException, SignatureException, - NoSuchAlgorithmException { - checkNotClosed(); - - if (!mV1SignaturePending) { - return null; - } - - if ((mInputJarManifestEntryDataRequest != null) - && (!mInputJarManifestEntryDataRequest.isDone())) { - throw new IllegalStateException( - "Still waiting to inspect input APK's " - + mInputJarManifestEntryDataRequest.getEntryName()); - } - - for (GetJarEntryDataDigestRequest digestRequest : mOutputJarEntryDigestRequests.values()) { - String entryName = digestRequest.getEntryName(); - if (!digestRequest.isDone()) { - throw new IllegalStateException( - "Still waiting to inspect output APK's " + entryName); - } - mOutputJarEntryDigests.put(entryName, digestRequest.getDigest()); - } - if (isEligibleForSourceStamp()) { - MessageDigest messageDigest = - MessageDigest.getInstance( - V1SchemeSigner.getJcaMessageDigestAlgorithm(mV1ContentDigestAlgorithm)); - messageDigest.update(generateSourceStampCertificateDigest()); - mOutputJarEntryDigests.put( - SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME, messageDigest.digest()); - } - mOutputJarEntryDigestRequests.clear(); - - for (GetJarEntryDataRequest dataRequest : mOutputSignatureJarEntryDataRequests.values()) { - if (!dataRequest.isDone()) { - throw new IllegalStateException( - "Still waiting to inspect output APK's " + dataRequest.getEntryName()); - } - } - - List apkSigningSchemeIds = new ArrayList<>(); - if (mV2SigningEnabled) { - apkSigningSchemeIds.add(ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2); - } - if (mV3SigningEnabled) { - apkSigningSchemeIds.add(ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3); - } - byte[] inputJarManifest = - (mInputJarManifestEntryDataRequest != null) - ? mInputJarManifestEntryDataRequest.getData() - : null; - if (isEligibleForSourceStamp()) { - inputJarManifest = - V1SchemeSigner.generateManifestFile( - mV1ContentDigestAlgorithm, - mOutputJarEntryDigests, - inputJarManifest) - .contents; - } - - // Check whether the most recently used signature (if present) is still fine. - checkOutputApkNotDebuggableIfDebuggableMustBeRejected(); - List> signatureZipEntries; - if ((mAddV1SignatureRequest == null) || (!mAddV1SignatureRequest.isDone())) { - try { - signatureZipEntries = - V1SchemeSigner.sign( - mV1SignerConfigs, - mV1ContentDigestAlgorithm, - mOutputJarEntryDigests, - apkSigningSchemeIds, - inputJarManifest, - mCreatedBy); - } catch (CertificateException e) { - throw new SignatureException("Failed to generate v1 signature", e); - } - } else { - V1SchemeSigner.OutputManifestFile newManifest = - V1SchemeSigner.generateManifestFile( - mV1ContentDigestAlgorithm, mOutputJarEntryDigests, inputJarManifest); - byte[] emittedSignatureManifest = - mEmittedSignatureJarEntryData.get(V1SchemeConstants.MANIFEST_ENTRY_NAME); - if (!Arrays.equals(newManifest.contents, emittedSignatureManifest)) { - // Emitted v1 signature is no longer valid. - try { - signatureZipEntries = - V1SchemeSigner.signManifest( - mV1SignerConfigs, - mV1ContentDigestAlgorithm, - apkSigningSchemeIds, - mCreatedBy, - newManifest); - } catch (CertificateException e) { - throw new SignatureException("Failed to generate v1 signature", e); - } - } else { - // Emitted v1 signature is still valid. Check whether the signature is there in the - // output. - signatureZipEntries = new ArrayList<>(); - for (Map.Entry expectedOutputEntry : - mEmittedSignatureJarEntryData.entrySet()) { - String entryName = expectedOutputEntry.getKey(); - byte[] expectedData = expectedOutputEntry.getValue(); - GetJarEntryDataRequest actualDataRequest = - mOutputSignatureJarEntryDataRequests.get(entryName); - if (actualDataRequest == null) { - // This signature entry hasn't been output. - signatureZipEntries.add(Pair.of(entryName, expectedData)); - continue; - } - byte[] actualData = actualDataRequest.getData(); - if (!Arrays.equals(expectedData, actualData)) { - signatureZipEntries.add(Pair.of(entryName, expectedData)); - } - } - if (signatureZipEntries.isEmpty()) { - // v1 signature in the output is valid - return null; - } - // v1 signature in the output is not valid. - } - } - - if (signatureZipEntries.isEmpty()) { - // v1 signature in the output is valid - mV1SignaturePending = false; - return null; - } - - List sigEntries = - new ArrayList<>(signatureZipEntries.size()); - for (Pair entry : signatureZipEntries) { - String entryName = entry.getFirst(); - byte[] entryData = entry.getSecond(); - sigEntries.add(new OutputJarSignatureRequest.JarEntry(entryName, entryData)); - mEmittedSignatureJarEntryData.put(entryName, entryData); - } - mAddV1SignatureRequest = new OutputJarSignatureRequestImpl(sigEntries); - return mAddV1SignatureRequest; - } - - @Deprecated - @Override - public OutputApkSigningBlockRequest outputZipSections( - DataSource zipEntries, DataSource zipCentralDirectory, DataSource zipEocd) - throws IOException, InvalidKeyException, SignatureException, NoSuchAlgorithmException { - return outputZipSectionsInternal(zipEntries, zipCentralDirectory, zipEocd, false); - } - - @Override - public OutputApkSigningBlockRequest2 outputZipSections2( - DataSource zipEntries, DataSource zipCentralDirectory, DataSource zipEocd) - throws IOException, InvalidKeyException, SignatureException, NoSuchAlgorithmException { - return outputZipSectionsInternal(zipEntries, zipCentralDirectory, zipEocd, true); - } - - private OutputApkSigningBlockRequestImpl outputZipSectionsInternal( - DataSource zipEntries, - DataSource zipCentralDirectory, - DataSource zipEocd, - boolean apkSigningBlockPaddingSupported) - throws IOException, InvalidKeyException, SignatureException, NoSuchAlgorithmException { - checkNotClosed(); - checkV1SigningDoneIfEnabled(); - if (!mV2SigningEnabled && !mV3SigningEnabled && !isEligibleForSourceStamp()) { - return null; - } - checkOutputApkNotDebuggableIfDebuggableMustBeRejected(); - - // adjust to proper padding - Pair paddingPair = - ApkSigningBlockUtils.generateApkSigningBlockPadding( - zipEntries, apkSigningBlockPaddingSupported); - DataSource beforeCentralDir = paddingPair.getFirst(); - int padSizeBeforeApkSigningBlock = paddingPair.getSecond(); - DataSource eocd = ApkSigningBlockUtils.copyWithModifiedCDOffset(beforeCentralDir, zipEocd); - - List> signingSchemeBlocks = new ArrayList<>(); - ApkSigningBlockUtils.SigningSchemeBlockAndDigests v2SigningSchemeBlockAndDigests = null; - ApkSigningBlockUtils.SigningSchemeBlockAndDigests v3SigningSchemeBlockAndDigests = null; - // If the engine is configured to preserve previous signature blocks and any were found in - // the existing APK signing block then add them to the list to be used to generate the - // new APK signing block. - if (mOtherSignersSignaturesPreserved && mPreservedSignatureBlocks != null - && !mPreservedSignatureBlocks.isEmpty()) { - signingSchemeBlocks.addAll(mPreservedSignatureBlocks); - } - - // create APK Signature Scheme V2 Signature if requested - if (mV2SigningEnabled) { - invalidateV2Signature(); - List v2SignerConfigs = - createV2SignerConfigs(apkSigningBlockPaddingSupported); - v2SigningSchemeBlockAndDigests = - V2SchemeSigner.generateApkSignatureSchemeV2Block( - mExecutor, - beforeCentralDir, - zipCentralDirectory, - eocd, - v2SignerConfigs, - mV3SigningEnabled, - mOtherSignersSignaturesPreserved ? mPreservedV2Signers : null); - signingSchemeBlocks.add(v2SigningSchemeBlockAndDigests.signingSchemeBlock); - } - if (mV3SigningEnabled) { - invalidateV3Signature(); - List v3SignerConfigs = - createV3SignerConfigs(apkSigningBlockPaddingSupported); - v3SigningSchemeBlockAndDigests = - V3SchemeSigner.generateApkSignatureSchemeV3Block( - mExecutor, - beforeCentralDir, - zipCentralDirectory, - eocd, - v3SignerConfigs); - signingSchemeBlocks.add(v3SigningSchemeBlockAndDigests.signingSchemeBlock); - } - if (isEligibleForSourceStamp()) { - ApkSigningBlockUtils.SignerConfig sourceStampSignerConfig = - createSourceStampSignerConfig(); - Map> signatureSchemeDigestInfos = - new HashMap<>(); - if (mV3SigningEnabled) { - signatureSchemeDigestInfos.put( - VERSION_APK_SIGNATURE_SCHEME_V3, v3SigningSchemeBlockAndDigests.digestInfo); - } - if (mV2SigningEnabled) { - signatureSchemeDigestInfos.put( - VERSION_APK_SIGNATURE_SCHEME_V2, v2SigningSchemeBlockAndDigests.digestInfo); - } - if (mV1SigningEnabled) { - Map v1SigningSchemeDigests = new HashMap<>(); - try { - // Jar signing related variables must have been already populated at this point - // if V1 signing is enabled since it is happening before computations on the APK - // signing block (V2/V3/V4/SourceStamp signing). - byte[] inputJarManifest = - (mInputJarManifestEntryDataRequest != null) - ? mInputJarManifestEntryDataRequest.getData() - : null; - byte[] jarManifest = - V1SchemeSigner.generateManifestFile( - mV1ContentDigestAlgorithm, - mOutputJarEntryDigests, - inputJarManifest) - .contents; - // The digest of the jar manifest does not need to be computed in chunks due to - // the small size of the manifest. - v1SigningSchemeDigests.put( - ContentDigestAlgorithm.SHA256, computeSha256DigestBytes(jarManifest)); - } catch (ApkFormatException e) { - throw new RuntimeException("Failed to generate manifest file", e); - } - signatureSchemeDigestInfos.put( - VERSION_JAR_SIGNATURE_SCHEME, v1SigningSchemeDigests); - } - signingSchemeBlocks.add( - V2SourceStampSigner.generateSourceStampBlock( - sourceStampSignerConfig, signatureSchemeDigestInfos)); - } - - // create APK Signing Block with v2 and/or v3 and/or SourceStamp blocks - byte[] apkSigningBlock = ApkSigningBlockUtils.generateApkSigningBlock(signingSchemeBlocks); - - mAddSigningBlockRequest = - new OutputApkSigningBlockRequestImpl(apkSigningBlock, padSizeBeforeApkSigningBlock); - return mAddSigningBlockRequest; - } - - @Override - public void outputDone() { - checkNotClosed(); - checkV1SigningDoneIfEnabled(); - checkSigningBlockDoneIfEnabled(); - } - - @Override - public void signV4(DataSource dataSource, File outputFile, boolean ignoreFailures) - throws SignatureException { - if (outputFile == null) { - if (ignoreFailures) { - return; - } - throw new SignatureException("Missing V4 output file."); - } - try { - ApkSigningBlockUtils.SignerConfig v4SignerConfig = createV4SignerConfig(); - V4SchemeSigner.generateV4Signature(dataSource, v4SignerConfig, outputFile); - } catch (InvalidKeyException | IOException | NoSuchAlgorithmException e) { - if (ignoreFailures) { - return; - } - throw new SignatureException("V4 signing failed", e); - } - } - - /** For external use only to generate V4 & tree separately. */ - public byte[] produceV4Signature(DataSource dataSource, OutputStream sigOutput) - throws SignatureException { - if (sigOutput == null) { - throw new SignatureException("Missing V4 output streams."); - } - try { - ApkSigningBlockUtils.SignerConfig v4SignerConfig = createV4SignerConfig(); - Pair pair = - V4SchemeSigner.generateV4Signature(dataSource, v4SignerConfig); - pair.getFirst().writeTo(sigOutput); - return pair.getSecond(); - } catch (InvalidKeyException | IOException | NoSuchAlgorithmException e) { - throw new SignatureException("V4 signing failed", e); - } - } - - @Override - public boolean isEligibleForSourceStamp() { - return mSourceStampSignerConfig != null - && (mV2SigningEnabled || mV3SigningEnabled || mV1SigningEnabled); - } - - @Override - public byte[] generateSourceStampCertificateDigest() throws SignatureException { - if (mSourceStampSignerConfig.getCertificates().isEmpty()) { - throw new SignatureException("No certificates configured for stamp"); - } - try { - return computeSha256DigestBytes( - mSourceStampSignerConfig.getCertificates().get(0).getEncoded()); - } catch (CertificateEncodingException e) { - throw new SignatureException("Failed to encode source stamp certificate", e); - } - } - - @Override - public void close() { - mClosed = true; - - mAddV1SignatureRequest = null; - mInputJarManifestEntryDataRequest = null; - mOutputAndroidManifestEntryDataRequest = null; - mDebuggable = null; - mOutputJarEntryDigestRequests.clear(); - mOutputJarEntryDigests.clear(); - mEmittedSignatureJarEntryData.clear(); - mOutputSignatureJarEntryDataRequests.clear(); - - mAddSigningBlockRequest = null; - } - - private void invalidateV1Signature() { - if (mV1SigningEnabled) { - mV1SignaturePending = true; - } - invalidateV2Signature(); - } - - private void invalidateV2Signature() { - if (mV2SigningEnabled) { - mV2SignaturePending = true; - mAddSigningBlockRequest = null; - } - } - - private void invalidateV3Signature() { - if (mV3SigningEnabled) { - mV3SignaturePending = true; - mAddSigningBlockRequest = null; - } - } - - private void checkNotClosed() { - if (mClosed) { - throw new IllegalStateException("Engine closed"); - } - } - - private void checkV1SigningDoneIfEnabled() { - if (!mV1SignaturePending) { - return; - } - - if (mAddV1SignatureRequest == null) { - throw new IllegalStateException( - "v1 signature (JAR signature) not yet generated. Skipped outputJarEntries()?"); - } - if (!mAddV1SignatureRequest.isDone()) { - throw new IllegalStateException( - "v1 signature (JAR signature) addition requested by outputJarEntries() hasn't" - + " been fulfilled"); - } - for (Map.Entry expectedOutputEntry : - mEmittedSignatureJarEntryData.entrySet()) { - String entryName = expectedOutputEntry.getKey(); - byte[] expectedData = expectedOutputEntry.getValue(); - GetJarEntryDataRequest actualDataRequest = - mOutputSignatureJarEntryDataRequests.get(entryName); - if (actualDataRequest == null) { - throw new IllegalStateException( - "APK entry " - + entryName - + " not yet output despite this having been" - + " requested"); - } else if (!actualDataRequest.isDone()) { - throw new IllegalStateException( - "Still waiting to inspect output APK's " + entryName); - } - byte[] actualData = actualDataRequest.getData(); - if (!Arrays.equals(expectedData, actualData)) { - throw new IllegalStateException( - "Output APK entry " + entryName + " data differs from what was requested"); - } - } - mV1SignaturePending = false; - } - - private void checkSigningBlockDoneIfEnabled() { - if (!mV2SignaturePending && !mV3SignaturePending) { - return; - } - if (mAddSigningBlockRequest == null) { - throw new IllegalStateException( - "Signed APK Signing BLock not yet generated. Skipped outputZipSections()?"); - } - if (!mAddSigningBlockRequest.isDone()) { - throw new IllegalStateException( - "APK Signing Block addition of signature(s) requested by" - + " outputZipSections() hasn't been fulfilled yet"); - } - mAddSigningBlockRequest = null; - mV2SignaturePending = false; - mV3SignaturePending = false; - } - - private void checkOutputApkNotDebuggableIfDebuggableMustBeRejected() throws SignatureException { - if (mDebuggableApkPermitted) { - return; - } - - try { - if (isOutputApkDebuggable()) { - throw new SignatureException( - "APK is debuggable (see android:debuggable attribute) and this engine is" - + " configured to refuse to sign debuggable APKs"); - } - } catch (ApkFormatException e) { - throw new SignatureException("Failed to determine whether the APK is debuggable", e); - } - } - - /** - * Returns whether the output APK is debuggable according to its {@code android:debuggable} - * declaration. - */ - private boolean isOutputApkDebuggable() throws ApkFormatException { - if (mDebuggable != null) { - return mDebuggable; - } - - if (mOutputAndroidManifestEntryDataRequest == null) { - throw new IllegalStateException( - "Cannot determine debuggable status of output APK because " - + ApkUtils.ANDROID_MANIFEST_ZIP_ENTRY_NAME - + " entry contents have not yet been requested"); - } - - if (!mOutputAndroidManifestEntryDataRequest.isDone()) { - throw new IllegalStateException( - "Still waiting to inspect output APK's " - + mOutputAndroidManifestEntryDataRequest.getEntryName()); - } - mDebuggable = - ApkUtils.getDebuggableFromBinaryAndroidManifest( - ByteBuffer.wrap(mOutputAndroidManifestEntryDataRequest.getData())); - return mDebuggable; - } - - private void forgetOutputApkDebuggableStatus() { - mDebuggable = null; - } - - /** Returns the output policy for the provided input JAR entry. */ - private InputJarEntryInstructions.OutputPolicy getInputJarEntryOutputPolicy(String entryName) { - if (mSignatureExpectedOutputJarEntryNames.contains(entryName)) { - return InputJarEntryInstructions.OutputPolicy.OUTPUT_BY_ENGINE; - } - if ((mOtherSignersSignaturesPreserved) - || (V1SchemeSigner.isJarEntryDigestNeededInManifest(entryName))) { - return InputJarEntryInstructions.OutputPolicy.OUTPUT; - } - return InputJarEntryInstructions.OutputPolicy.SKIP; - } - - private static class OutputJarSignatureRequestImpl implements OutputJarSignatureRequest { - private final List mAdditionalJarEntries; - private volatile boolean mDone; - - private OutputJarSignatureRequestImpl(List additionalZipEntries) { - mAdditionalJarEntries = - Collections.unmodifiableList(new ArrayList<>(additionalZipEntries)); - } - - @Override - public List getAdditionalJarEntries() { - return mAdditionalJarEntries; - } - - @Override - public void done() { - mDone = true; - } - - private boolean isDone() { - return mDone; - } - } - - @SuppressWarnings("deprecation") - private static class OutputApkSigningBlockRequestImpl - implements OutputApkSigningBlockRequest, OutputApkSigningBlockRequest2 { - private final byte[] mApkSigningBlock; - private final int mPaddingBeforeApkSigningBlock; - private volatile boolean mDone; - - private OutputApkSigningBlockRequestImpl(byte[] apkSigingBlock, int paddingBefore) { - mApkSigningBlock = apkSigingBlock.clone(); - mPaddingBeforeApkSigningBlock = paddingBefore; - } - - @Override - public byte[] getApkSigningBlock() { - return mApkSigningBlock.clone(); - } - - @Override - public void done() { - mDone = true; - } - - private boolean isDone() { - return mDone; - } - - @Override - public int getPaddingSizeBeforeApkSigningBlock() { - return mPaddingBeforeApkSigningBlock; - } - } - - /** JAR entry inspection request which obtain the entry's uncompressed data. */ - private static class GetJarEntryDataRequest implements InspectJarEntryRequest { - private final String mEntryName; - private final Object mLock = new Object(); - - private boolean mDone; - private DataSink mDataSink; - private ByteArrayOutputStream mDataSinkBuf; - - private GetJarEntryDataRequest(String entryName) { - mEntryName = entryName; - } - - @Override - public String getEntryName() { - return mEntryName; - } - - @Override - public DataSink getDataSink() { - synchronized (mLock) { - checkNotDone(); - if (mDataSinkBuf == null) { - mDataSinkBuf = new ByteArrayOutputStream(); - } - if (mDataSink == null) { - mDataSink = DataSinks.asDataSink(mDataSinkBuf); - } - return mDataSink; - } - } - - @Override - public void done() { - synchronized (mLock) { - if (mDone) { - return; - } - mDone = true; - } - } - - private boolean isDone() { - synchronized (mLock) { - return mDone; - } - } - - private void checkNotDone() throws IllegalStateException { - synchronized (mLock) { - if (mDone) { - throw new IllegalStateException("Already done"); - } - } - } - - private byte[] getData() { - synchronized (mLock) { - if (!mDone) { - throw new IllegalStateException("Not yet done"); - } - return (mDataSinkBuf != null) ? mDataSinkBuf.toByteArray() : new byte[0]; - } - } - } - - /** JAR entry inspection request which obtains the digest of the entry's uncompressed data. */ - private static class GetJarEntryDataDigestRequest implements InspectJarEntryRequest { - private final String mEntryName; - private final String mJcaDigestAlgorithm; - private final Object mLock = new Object(); - - private boolean mDone; - private DataSink mDataSink; - private MessageDigest mMessageDigest; - private byte[] mDigest; - - private GetJarEntryDataDigestRequest(String entryName, String jcaDigestAlgorithm) { - mEntryName = entryName; - mJcaDigestAlgorithm = jcaDigestAlgorithm; - } - - @Override - public String getEntryName() { - return mEntryName; - } - - @Override - public DataSink getDataSink() { - synchronized (mLock) { - checkNotDone(); - if (mDataSink == null) { - mDataSink = DataSinks.asDataSink(getMessageDigest()); - } - return mDataSink; - } - } - - private MessageDigest getMessageDigest() { - synchronized (mLock) { - if (mMessageDigest == null) { - try { - mMessageDigest = MessageDigest.getInstance(mJcaDigestAlgorithm); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException( - mJcaDigestAlgorithm + " MessageDigest not available", e); - } - } - return mMessageDigest; - } - } - - @Override - public void done() { - synchronized (mLock) { - if (mDone) { - return; - } - mDone = true; - mDigest = getMessageDigest().digest(); - mMessageDigest = null; - mDataSink = null; - } - } - - private boolean isDone() { - synchronized (mLock) { - return mDone; - } - } - - private void checkNotDone() throws IllegalStateException { - synchronized (mLock) { - if (mDone) { - throw new IllegalStateException("Already done"); - } - } - } - - private byte[] getDigest() { - synchronized (mLock) { - if (!mDone) { - throw new IllegalStateException("Not yet done"); - } - return mDigest.clone(); - } - } - } - - /** JAR entry inspection request which transparently satisfies multiple such requests. */ - private static class CompoundInspectJarEntryRequest implements InspectJarEntryRequest { - private final String mEntryName; - private final InspectJarEntryRequest[] mRequests; - private final Object mLock = new Object(); - - private DataSink mSink; - - private CompoundInspectJarEntryRequest( - String entryName, InspectJarEntryRequest... requests) { - mEntryName = entryName; - mRequests = requests; - } - - @Override - public String getEntryName() { - return mEntryName; - } - - @Override - public DataSink getDataSink() { - synchronized (mLock) { - if (mSink == null) { - DataSink[] sinks = new DataSink[mRequests.length]; - for (int i = 0; i < sinks.length; i++) { - sinks[i] = mRequests[i].getDataSink(); - } - mSink = new TeeDataSink(sinks); - } - return mSink; - } - } - - @Override - public void done() { - for (InspectJarEntryRequest request : mRequests) { - request.done(); - } - } - } - - /** - * Configuration of a signer. - * - *

Use {@link Builder} to obtain configuration instances. - */ - public static class SignerConfig { - private final String mName; - private final PrivateKey mPrivateKey; - private final List mCertificates; - private final boolean mDeterministicDsaSigning; - - private SignerConfig( - String name, PrivateKey privateKey, List certificates, - boolean deterministicDsaSigning) { - mName = name; - mPrivateKey = privateKey; - mCertificates = Collections.unmodifiableList(new ArrayList<>(certificates)); - mDeterministicDsaSigning = deterministicDsaSigning; - } - - /** Returns the name of this signer. */ - public String getName() { - return mName; - } - - /** Returns the signing key of this signer. */ - public PrivateKey getPrivateKey() { - return mPrivateKey; - } - - /** - * Returns the certificate(s) of this signer. The first certificate's public key corresponds - * to this signer's private key. - */ - public List getCertificates() { - return mCertificates; - } - - /** - * If this signer is a DSA signer, whether or not the signing is done deterministically. - */ - public boolean getDeterministicDsaSigning() { - return mDeterministicDsaSigning; - } - - /** Builder of {@link SignerConfig} instances. */ - public static class Builder { - private final String mName; - private final PrivateKey mPrivateKey; - private final List mCertificates; - private final boolean mDeterministicDsaSigning; - - /** - * Constructs a new {@code Builder}. - * - * @param name signer's name. The name is reflected in the name of files comprising the - * JAR signature of the APK. - * @param privateKey signing key - * @param certificates list of one or more X.509 certificates. The subject public key of - * the first certificate must correspond to the {@code privateKey}. - */ - public Builder(String name, PrivateKey privateKey, List certificates) { - this(name, privateKey, certificates, false); - } - - /** - * Constructs a new {@code Builder}. - * - * @param name signer's name. The name is reflected in the name of files comprising the - * JAR signature of the APK. - * @param privateKey signing key - * @param certificates list of one or more X.509 certificates. The subject public key of - * the first certificate must correspond to the {@code privateKey}. - * @param deterministicDsaSigning When signing using DSA, whether or not the - * deterministic signing algorithm variant (RFC6979) should be used. - */ - public Builder(String name, PrivateKey privateKey, List certificates, - boolean deterministicDsaSigning) { - if (name.isEmpty()) { - throw new IllegalArgumentException("Empty name"); - } - mName = name; - mPrivateKey = privateKey; - mCertificates = new ArrayList<>(certificates); - mDeterministicDsaSigning = deterministicDsaSigning; - } - - /** - * Returns a new {@code SignerConfig} instance configured based on the configuration of - * this builder. - */ - public SignerConfig build() { - return new SignerConfig(mName, mPrivateKey, mCertificates, - mDeterministicDsaSigning); - } - } - } - - /** Builder of {@link DefaultApkSignerEngine} instances. */ - public static class Builder { - private List mSignerConfigs; - private SignerConfig mStampSignerConfig; - private SigningCertificateLineage mSourceStampSigningCertificateLineage; - private final int mMinSdkVersion; - - private boolean mV1SigningEnabled = true; - private boolean mV2SigningEnabled = true; - private boolean mV3SigningEnabled = true; - private boolean mVerityEnabled = false; - private boolean mDebuggableApkPermitted = true; - private boolean mOtherSignersSignaturesPreserved; - private String mCreatedBy = "1.0 (Android)"; - - private SigningCertificateLineage mSigningCertificateLineage; - - // APK Signature Scheme v3 only supports a single signing certificate, so to move to v3 - // signing by default, but not require prior clients to update to explicitly disable v3 - // signing for multiple signers, we modify the mV3SigningEnabled depending on the provided - // inputs (multiple signers and mSigningCertificateLineage in particular). Maintain two - // extra variables to record whether or not mV3SigningEnabled has been set directly by a - // client and so should override the default behavior. - private boolean mV3SigningExplicitlyDisabled = false; - private boolean mV3SigningExplicitlyEnabled = false; - - /** - * Constructs a new {@code Builder}. - * - * @param signerConfigs information about signers with which the APK will be signed. At - * least one signer configuration must be provided. - * @param minSdkVersion API Level of the oldest Android platform on which the APK is - * supposed to be installed. See {@code minSdkVersion} attribute in the APK's {@code - * AndroidManifest.xml}. The higher the version, the stronger signing features will be - * enabled. - */ - public Builder(List signerConfigs, int minSdkVersion) { - if (signerConfigs.isEmpty()) { - throw new IllegalArgumentException("At least one signer config must be provided"); - } - if (signerConfigs.size() > 1) { - // APK Signature Scheme v3 only supports single signer, unless a - // SigningCertificateLineage is provided, in which case this will be reset to true, - // since we don't yet have a v4 scheme about which to worry - mV3SigningEnabled = false; - } - mSignerConfigs = new ArrayList<>(signerConfigs); - mMinSdkVersion = minSdkVersion; - } - - /** - * Returns a new {@code DefaultApkSignerEngine} instance configured based on the - * configuration of this builder. - */ - public DefaultApkSignerEngine build() throws InvalidKeyException { - - if (mV3SigningExplicitlyDisabled && mV3SigningExplicitlyEnabled) { - throw new IllegalStateException( - "Builder configured to both enable and disable APK " - + "Signature Scheme v3 signing"); - } - if (mV3SigningExplicitlyDisabled) { - mV3SigningEnabled = false; - } else if (mV3SigningExplicitlyEnabled) { - mV3SigningEnabled = true; - } - - // make sure our signers are appropriately setup - if (mSigningCertificateLineage != null) { - try { - mSignerConfigs = mSigningCertificateLineage.sortSignerConfigs(mSignerConfigs); - if (!mV3SigningEnabled && mSignerConfigs.size() > 1) { - - // this is a strange situation: we've provided a valid rotation history, but - // are only signing with v1/v2. blow up, since we don't know for sure with - // which signer the user intended to sign - throw new IllegalStateException( - "Provided multiple signers which are part of the" - + " SigningCertificateLineage, but not signing with APK" - + " Signature Scheme v3"); - } - } catch (IllegalArgumentException e) { - throw new IllegalStateException( - "Provided signer configs do not match the " - + "provided SigningCertificateLineage", - e); - } - } else if (mV3SigningEnabled && mSignerConfigs.size() > 1) { - throw new IllegalStateException( - "Multiple signing certificates provided for use with APK Signature Scheme" - + " v3 without an accompanying SigningCertificateLineage"); - } - - return new DefaultApkSignerEngine( - mSignerConfigs, - mStampSignerConfig, - mSourceStampSigningCertificateLineage, - mMinSdkVersion, - mV1SigningEnabled, - mV2SigningEnabled, - mV3SigningEnabled, - mVerityEnabled, - mDebuggableApkPermitted, - mOtherSignersSignaturesPreserved, - mCreatedBy, - mSigningCertificateLineage); - } - - /** Sets the signer configuration for the SourceStamp to be embedded in the APK. */ - public Builder setStampSignerConfig(SignerConfig stampSignerConfig) { - mStampSignerConfig = stampSignerConfig; - return this; - } - - /** - * Sets the source stamp {@link SigningCertificateLineage}. This structure provides proof of - * signing certificate rotation for certificates previously used to sign source stamps. - */ - public Builder setSourceStampSigningCertificateLineage( - SigningCertificateLineage sourceStampSigningCertificateLineage) { - mSourceStampSigningCertificateLineage = sourceStampSigningCertificateLineage; - return this; - } - - /** - * Sets whether the APK should be signed using JAR signing (aka v1 signature scheme). - * - *

By default, the APK will be signed using this scheme. - */ - public Builder setV1SigningEnabled(boolean enabled) { - mV1SigningEnabled = enabled; - return this; - } - - /** - * Sets whether the APK should be signed using APK Signature Scheme v2 (aka v2 signature - * scheme). - * - *

By default, the APK will be signed using this scheme. - */ - public Builder setV2SigningEnabled(boolean enabled) { - mV2SigningEnabled = enabled; - return this; - } - - /** - * Sets whether the APK should be signed using APK Signature Scheme v3 (aka v3 signature - * scheme). - * - *

By default, the APK will be signed using this scheme. - */ - public Builder setV3SigningEnabled(boolean enabled) { - mV3SigningEnabled = enabled; - if (enabled) { - mV3SigningExplicitlyEnabled = true; - } else { - mV3SigningExplicitlyDisabled = true; - } - return this; - } - - /** - * Sets whether the APK should be signed using the verity signature algorithm in the v2 and - * v3 signature blocks. - * - *

By default, the APK will be signed using the verity signature algorithm for the v2 and - * v3 signature schemes. - */ - public Builder setVerityEnabled(boolean enabled) { - mVerityEnabled = enabled; - return this; - } - - /** - * Sets whether the APK should be signed even if it is marked as debuggable ({@code - * android:debuggable="true"} in its {@code AndroidManifest.xml}). For backward - * compatibility reasons, the default value of this setting is {@code true}. - * - *

It is dangerous to sign debuggable APKs with production/release keys because Android - * platform loosens security checks for such APKs. For example, arbitrary unauthorized code - * may be executed in the context of such an app by anybody with ADB shell access. - */ - public Builder setDebuggableApkPermitted(boolean permitted) { - mDebuggableApkPermitted = permitted; - return this; - } - - /** - * Sets whether signatures produced by signers other than the ones configured in this engine - * should be copied from the input APK to the output APK. - * - *

By default, signatures of other signers are omitted from the output APK. - */ - public Builder setOtherSignersSignaturesPreserved(boolean preserved) { - mOtherSignersSignaturesPreserved = preserved; - return this; - } - - /** Sets the value of the {@code Created-By} field in JAR signature files. */ - public Builder setCreatedBy(String createdBy) { - if (createdBy == null) { - throw new NullPointerException(); - } - mCreatedBy = createdBy; - return this; - } - - /** - * Sets the {@link SigningCertificateLineage} to use with the v3 signature scheme. This - * structure provides proof of signing certificate rotation linking {@link SignerConfig} - * objects to previous ones. - */ - public Builder setSigningCertificateLineage( - SigningCertificateLineage signingCertificateLineage) { - if (signingCertificateLineage != null) { - mV3SigningEnabled = true; - mSigningCertificateLineage = signingCertificateLineage; - } - return this; - } - } -} diff --git a/app/src/main/java/com/android/apksig/Hints.java b/app/src/main/java/com/android/apksig/Hints.java deleted file mode 100644 index 4070fa231a..0000000000 --- a/app/src/main/java/com/android/apksig/Hints.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.apksig; -import java.io.IOException; -import java.io.DataOutputStream; -import java.io.ByteArrayOutputStream; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public final class Hints { - /** - * Name of hint pattern asset file in APK. - */ - public static final String PIN_HINT_ASSET_ZIP_ENTRY_NAME = "assets/com.android.hints.pins.txt"; - - /** - * Name of hint byte range data file in APK. Keep in sync with PinnerService.java. - */ - public static final String PIN_BYTE_RANGE_ZIP_ENTRY_NAME = "pinlist.meta"; - - private static int clampToInt(long value) { - return (int) Math.max(0, Math.min(value, Integer.MAX_VALUE)); - } - - public static final class ByteRange { - final long start; - final long end; - - public ByteRange(long start, long end) { - this.start = start; - this.end = end; - } - } - - public static final class PatternWithRange { - final Pattern pattern; - final long offset; - final long size; - - public PatternWithRange(String pattern) { - this.pattern = Pattern.compile(pattern); - this.offset= 0; - this.size = Long.MAX_VALUE; - } - - public PatternWithRange(String pattern, long offset, long size) { - this.pattern = Pattern.compile(pattern); - this.offset = offset; - this.size = size; - } - - public Matcher matcher(CharSequence input) { - return this.pattern.matcher(input); - } - - public ByteRange ClampToAbsoluteByteRange(ByteRange rangeIn) { - if (rangeIn.end - rangeIn.start < this.offset) { - return null; - } - long rangeOutStart = rangeIn.start + this.offset; - long rangeOutSize = Math.min(rangeIn.end - rangeOutStart, - this.size); - return new ByteRange(rangeOutStart, - rangeOutStart + rangeOutSize); - } - } - - /** - * Create a blob of bytes that PinnerService understands as a - * sequence of byte ranges to pin. - */ - public static byte[] encodeByteRangeList(List pinByteRanges) { - ByteArrayOutputStream bos = new ByteArrayOutputStream(pinByteRanges.size() * 8); - DataOutputStream out = new DataOutputStream(bos); - try { - for (ByteRange pinByteRange : pinByteRanges) { - out.writeInt(clampToInt(pinByteRange.start)); - out.writeInt(clampToInt(pinByteRange.end - pinByteRange.start)); - } - } catch (IOException ex) { - throw new AssertionError("impossible", ex); - } - return bos.toByteArray(); - } - - public static ArrayList parsePinPatterns(byte[] patternBlob) { - ArrayList pinPatterns = new ArrayList<>(); - try { - for (String rawLine : new String(patternBlob, "UTF-8").split("\n")) { - String line = rawLine.replaceFirst("#.*", ""); // # starts a comment - String[] fields = line.split(" "); - if (fields.length == 1) { - pinPatterns.add(new PatternWithRange(fields[0])); - } else if (fields.length == 3) { - long start = Long.parseLong(fields[1]); - long end = Long.parseLong(fields[2]); - pinPatterns.add(new PatternWithRange(fields[0], start, end - start)); - } else { - throw new AssertionError("bad pin pattern line " + line); - } - } - } catch (UnsupportedEncodingException ex) { - throw new RuntimeException("UTF-8 must be supported", ex); - } - return pinPatterns; - } -} diff --git a/app/src/main/java/com/android/apksig/SigningCertificateLineage.java b/app/src/main/java/com/android/apksig/SigningCertificateLineage.java deleted file mode 100644 index 767a1f6202..0000000000 --- a/app/src/main/java/com/android/apksig/SigningCertificateLineage.java +++ /dev/null @@ -1,1076 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig; - -import static com.android.apksig.internal.apk.ApkSigningBlockUtils.getLengthPrefixedSlice; - -import com.android.apksig.apk.ApkFormatException; -import com.android.apksig.apk.ApkUtils; -import com.android.apksig.internal.apk.ApkSigningBlockUtils; -import com.android.apksig.internal.apk.SignatureAlgorithm; -import com.android.apksig.internal.apk.SignatureInfo; -import com.android.apksig.internal.apk.v3.V3SchemeConstants; -import com.android.apksig.internal.apk.v3.V3SchemeSigner; -import com.android.apksig.internal.apk.v3.V3SigningCertificateLineage; -import com.android.apksig.internal.apk.v3.V3SigningCertificateLineage.SigningCertificateNode; -import com.android.apksig.internal.util.AndroidSdkVersion; -import com.android.apksig.internal.util.ByteBufferUtils; -import com.android.apksig.internal.util.Pair; -import com.android.apksig.internal.util.RandomAccessFileDataSink; -import com.android.apksig.util.DataSink; -import com.android.apksig.util.DataSource; -import com.android.apksig.util.DataSources; -import com.android.apksig.zip.ZipFormatException; - -import java.io.File; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.SignatureException; -import java.security.cert.CertificateEncodingException; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -/** - * APK Signer Lineage. - * - *

The signer lineage contains a history of signing certificates with each ancestor attesting to - * the validity of its descendant. Each additional descendant represents a new identity that can be - * used to sign an APK, and each generation has accompanying attributes which represent how the - * APK would like to view the older signing certificates, specifically how they should be trusted in - * certain situations. - * - *

Its primary use is to enable APK Signing Certificate Rotation. The Android platform verifies - * the APK Signer Lineage, and if the current signing certificate for the APK is in the Signer - * Lineage, and the Lineage contains the certificate the platform associates with the APK, it will - * allow upgrades to the new certificate. - * - * @see Application Signing - */ -public class SigningCertificateLineage { - - public final static int MAGIC = 0x3eff39d1; - - private final static int FIRST_VERSION = 1; - - private static final int CURRENT_VERSION = FIRST_VERSION; - - /** accept data from already installed pkg with this cert */ - private static final int PAST_CERT_INSTALLED_DATA = 1; - - /** accept sharedUserId with pkg with this cert */ - private static final int PAST_CERT_SHARED_USER_ID = 2; - - /** grant SIGNATURE permissions to pkgs with this cert */ - private static final int PAST_CERT_PERMISSION = 4; - - /** - * Enable updates back to this certificate. WARNING: this effectively removes any benefit of - * signing certificate changes, since a compromised key could retake control of an app even - * after change, and should only be used if there is a problem encountered when trying to ditch - * an older cert. - */ - private static final int PAST_CERT_ROLLBACK = 8; - - /** - * Preserve authenticator module-based access in AccountManager gated by signing certificate. - */ - private static final int PAST_CERT_AUTH = 16; - - private final int mMinSdkVersion; - - /** - * The signing lineage is just a list of nodes, with the first being the original signing - * certificate and the most recent being the one with which the APK is to actually be signed. - */ - private final List mSigningLineage; - - private SigningCertificateLineage(int minSdkVersion, List list) { - mMinSdkVersion = minSdkVersion; - mSigningLineage = list; - } - - private static SigningCertificateLineage createSigningLineage( - int minSdkVersion, SignerConfig parent, SignerCapabilities parentCapabilities, - SignerConfig child, SignerCapabilities childCapabilities) - throws CertificateEncodingException, InvalidKeyException, NoSuchAlgorithmException, - SignatureException { - SigningCertificateLineage signingCertificateLineage = - new SigningCertificateLineage(minSdkVersion, new ArrayList<>()); - signingCertificateLineage = - signingCertificateLineage.spawnFirstDescendant(parent, parentCapabilities); - return signingCertificateLineage.spawnDescendant(parent, child, childCapabilities); - } - - public static SigningCertificateLineage readFromFile(File file) - throws IOException { - if (file == null) { - throw new NullPointerException("file == null"); - } - RandomAccessFile inputFile = new RandomAccessFile(file, "r"); - return readFromDataSource(DataSources.asDataSource(inputFile)); - } - - public static SigningCertificateLineage readFromDataSource(DataSource dataSource) - throws IOException { - if (dataSource == null) { - throw new NullPointerException("dataSource == null"); - } - ByteBuffer inBuff = dataSource.getByteBuffer(0, (int) dataSource.size()); - inBuff.order(ByteOrder.LITTLE_ENDIAN); - return read(inBuff); - } - - /** - * Extracts a Signing Certificate Lineage from a v3 signer proof-of-rotation attribute. - * - * - * this may not give a complete representation of an APK's signing certificate history, - * since the APK may have multiple signers corresponding to different platform versions. - * Use readFromApkFile to handle this case. - * - * @param attrValue - */ - public static SigningCertificateLineage readFromV3AttributeValue(byte[] attrValue) - throws IOException { - List parsedLineage = - V3SigningCertificateLineage.readSigningCertificateLineage(ByteBuffer.wrap( - attrValue).order(ByteOrder.LITTLE_ENDIAN)); - int minSdkVersion = calculateMinSdkVersion(parsedLineage); - return new SigningCertificateLineage(minSdkVersion, parsedLineage); - } - - /** - * Extracts a Signing Certificate Lineage from the proof-of-rotation attribute in the V3 - * signature block of the provided APK File. - * - * @throws IllegalArgumentException if the provided APK does not contain a V3 signature block, - * or if the V3 signature block does not contain a valid lineage. - */ - public static SigningCertificateLineage readFromApkFile(File apkFile) - throws IOException, ApkFormatException { - try (RandomAccessFile f = new RandomAccessFile(apkFile, "r")) { - DataSource apk = DataSources.asDataSource(f, 0, f.length()); - return readFromApkDataSource(apk); - } - } - - /** - * Extracts a Signing Certificate Lineage from the proof-of-rotation attribute in the V3 - * signature block of the provided APK DataSource. - * - * @throws IllegalArgumentException if the provided APK does not contain a V3 signature block, - * or if the V3 signature block does not contain a valid lineage. - */ - public static SigningCertificateLineage readFromApkDataSource(DataSource apk) - throws IOException, ApkFormatException { - SignatureInfo signatureInfo; - try { - ApkUtils.ZipSections zipSections = ApkUtils.findZipSections(apk); - ApkSigningBlockUtils.Result result = new ApkSigningBlockUtils.Result( - ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3); - signatureInfo = - ApkSigningBlockUtils.findSignature(apk, zipSections, - V3SchemeConstants.APK_SIGNATURE_SCHEME_V3_BLOCK_ID, result); - } catch (ZipFormatException e) { - throw new ApkFormatException(e.getMessage()); - } catch (ApkSigningBlockUtils.SignatureNotFoundException e) { - throw new IllegalArgumentException( - "The provided APK does not contain a valid V3 signature block."); - } - - // FORMAT: - // * length-prefixed sequence of length-prefixed signers: - // * length-prefixed signed data - // * minSDK - // * maxSDK - // * length-prefixed sequence of length-prefixed signatures - // * length-prefixed public key - ByteBuffer signers = getLengthPrefixedSlice(signatureInfo.signatureBlock); - List lineages = new ArrayList<>(1); - while (signers.hasRemaining()) { - ByteBuffer signer = getLengthPrefixedSlice(signers); - ByteBuffer signedData = getLengthPrefixedSlice(signer); - try { - SigningCertificateLineage lineage = readFromSignedData(signedData); - lineages.add(lineage); - } catch (IllegalArgumentException ignored) { - // The current signer block does not contain a valid lineage, but it is possible - // another block will. - } - } - SigningCertificateLineage result; - if (lineages.isEmpty()) { - throw new IllegalArgumentException( - "The provided APK does not contain a valid lineage."); - } else if (lineages.size() > 1) { - result = consolidateLineages(lineages); - } else { - result = lineages.get(0); - } - return result; - } - - /** - * Extracts a Signing Certificate Lineage from the proof-of-rotation attribute in the provided - * signed data portion of a signer in a V3 signature block. - * - * @throws IllegalArgumentException if the provided signed data does not contain a valid - * lineage. - */ - public static SigningCertificateLineage readFromSignedData(ByteBuffer signedData) - throws IOException, ApkFormatException { - // FORMAT: - // * length-prefixed sequence of length-prefixed digests: - // * length-prefixed sequence of certificates: - // * length-prefixed bytes: X.509 certificate (ASN.1 DER encoded). - // * uint-32: minSdkVersion - // * uint-32: maxSdkVersion - // * length-prefixed sequence of length-prefixed additional attributes: - // * uint32: ID - // * (length - 4) bytes: value - // * uint32: Proof-of-rotation ID: 0x3ba06f8c - // * length-prefixed proof-of-rotation structure - // consume the digests through the maxSdkVersion to reach the lineage in the attributes - getLengthPrefixedSlice(signedData); - getLengthPrefixedSlice(signedData); - signedData.getInt(); - signedData.getInt(); - // iterate over the additional attributes adding any lineages to the List - ByteBuffer additionalAttributes = getLengthPrefixedSlice(signedData); - List lineages = new ArrayList<>(1); - while (additionalAttributes.hasRemaining()) { - ByteBuffer attribute = getLengthPrefixedSlice(additionalAttributes); - int id = attribute.getInt(); - if (id == V3SchemeConstants.PROOF_OF_ROTATION_ATTR_ID) { - byte[] value = ByteBufferUtils.toByteArray(attribute); - SigningCertificateLineage lineage = readFromV3AttributeValue(value); - lineages.add(lineage); - } - } - SigningCertificateLineage result; - // There should only be a single attribute with the lineage, but if there are multiple then - // attempt to consolidate the lineages. - if (lineages.isEmpty()) { - throw new IllegalArgumentException("The signed data does not contain a valid lineage."); - } else if (lineages.size() > 1) { - result = consolidateLineages(lineages); - } else { - result = lineages.get(0); - } - return result; - } - - public void writeToFile(File file) throws IOException { - if (file == null) { - throw new NullPointerException("file == null"); - } - RandomAccessFile outputFile = new RandomAccessFile(file, "rw"); - writeToDataSink(new RandomAccessFileDataSink(outputFile)); - } - - public void writeToDataSink(DataSink dataSink) throws IOException { - if (dataSink == null) { - throw new NullPointerException("dataSink == null"); - } - dataSink.consume(write()); - } - - /** - * Add a new signing certificate to the lineage. This effectively creates a signing certificate - * rotation event, forcing APKs which include this lineage to be signed by the new signer. The - * flags associated with the new signer are set to a default value. - * - * @param parent current signing certificate of the containing APK - * @param child new signing certificate which will sign the APK contents - */ - public SigningCertificateLineage spawnDescendant(SignerConfig parent, SignerConfig child) - throws CertificateEncodingException, InvalidKeyException, NoSuchAlgorithmException, - SignatureException { - if (parent == null || child == null) { - throw new NullPointerException("can't add new descendant to lineage with null inputs"); - } - SignerCapabilities signerCapabilities = new SignerCapabilities.Builder().build(); - return spawnDescendant(parent, child, signerCapabilities); - } - - /** - * Add a new signing certificate to the lineage. This effectively creates a signing certificate - * rotation event, forcing APKs which include this lineage to be signed by the new signer. - * - * @param parent current signing certificate of the containing APK - * @param child new signing certificate which will sign the APK contents - * @param childCapabilities flags - */ - public SigningCertificateLineage spawnDescendant( - SignerConfig parent, SignerConfig child, SignerCapabilities childCapabilities) - throws CertificateEncodingException, InvalidKeyException, - NoSuchAlgorithmException, SignatureException { - if (parent == null) { - throw new NullPointerException("parent == null"); - } - if (child == null) { - throw new NullPointerException("child == null"); - } - if (childCapabilities == null) { - throw new NullPointerException("childCapabilities == null"); - } - if (mSigningLineage.isEmpty()) { - throw new IllegalArgumentException("Cannot spawn descendant signing certificate on an" - + " empty SigningCertificateLineage: no parent node"); - } - - // make sure that the parent matches our newest generation (leaf node/sink) - SigningCertificateNode currentGeneration = mSigningLineage.get(mSigningLineage.size() - 1); - if (!Arrays.equals(currentGeneration.signingCert.getEncoded(), - parent.getCertificate().getEncoded())) { - throw new IllegalArgumentException("SignerConfig Certificate containing private key" - + " to sign the new SigningCertificateLineage record does not match the" - + " existing most recent record"); - } - - // create data to be signed, including the algorithm we're going to use - SignatureAlgorithm signatureAlgorithm = getSignatureAlgorithm(parent); - ByteBuffer prefixedSignedData = ByteBuffer.wrap( - V3SigningCertificateLineage.encodeSignedData( - child.getCertificate(), signatureAlgorithm.getId())); - prefixedSignedData.position(4); - ByteBuffer signedDataBuffer = ByteBuffer.allocate(prefixedSignedData.remaining()); - signedDataBuffer.put(prefixedSignedData); - byte[] signedData = signedDataBuffer.array(); - - // create SignerConfig to do the signing - List certificates = new ArrayList<>(1); - certificates.add(parent.getCertificate()); - ApkSigningBlockUtils.SignerConfig newSignerConfig = - new ApkSigningBlockUtils.SignerConfig(); - newSignerConfig.privateKey = parent.getPrivateKey(); - newSignerConfig.certificates = certificates; - newSignerConfig.signatureAlgorithms = Collections.singletonList(signatureAlgorithm); - - // sign it - List> signatures = - ApkSigningBlockUtils.generateSignaturesOverData(newSignerConfig, signedData); - - // finally, add it to our lineage - SignatureAlgorithm sigAlgorithm = SignatureAlgorithm.findById(signatures.get(0).getFirst()); - byte[] signature = signatures.get(0).getSecond(); - currentGeneration.sigAlgorithm = sigAlgorithm; - SigningCertificateNode childNode = - new SigningCertificateNode( - child.getCertificate(), sigAlgorithm, null, - signature, childCapabilities.getFlags()); - List lineageCopy = new ArrayList<>(mSigningLineage); - lineageCopy.add(childNode); - return new SigningCertificateLineage(mMinSdkVersion, lineageCopy); - } - - /** - * The number of signing certificates in the lineage, including the current signer, which means - * this value can also be used to V2determine the number of signing certificate rotations by - * subtracting 1. - */ - public int size() { - return mSigningLineage.size(); - } - - private SignatureAlgorithm getSignatureAlgorithm(SignerConfig parent) - throws InvalidKeyException { - PublicKey publicKey = parent.getCertificate().getPublicKey(); - - // TODO switch to one signature algorithm selection, or add support for multiple algorithms - List algorithms = V3SchemeSigner.getSuggestedSignatureAlgorithms( - publicKey, mMinSdkVersion, false /* verityEnabled */, - false /* deterministicDsaSigning */); - return algorithms.get(0); - } - - private SigningCertificateLineage spawnFirstDescendant( - SignerConfig parent, SignerCapabilities signerCapabilities) { - if (!mSigningLineage.isEmpty()) { - throw new IllegalStateException("SigningCertificateLineage already has its first node"); - } - - // check to make sure that the public key for the first node is acceptable for our minSdk - try { - getSignatureAlgorithm(parent); - } catch (InvalidKeyException e) { - throw new IllegalArgumentException("Algorithm associated with first signing certificate" - + " invalid on desired platform versions", e); - } - - // create "fake" signed data (there will be no signature over it, since there is no parent - SigningCertificateNode firstNode = new SigningCertificateNode( - parent.getCertificate(), null, null, new byte[0], signerCapabilities.getFlags()); - return new SigningCertificateLineage(mMinSdkVersion, Collections.singletonList(firstNode)); - } - - private static SigningCertificateLineage read(ByteBuffer inputByteBuffer) - throws IOException { - ApkSigningBlockUtils.checkByteOrderLittleEndian(inputByteBuffer); - if (inputByteBuffer.remaining() < 8) { - throw new IllegalArgumentException( - "Improper SigningCertificateLineage format: insufficient data for header."); - } - - if (inputByteBuffer.getInt() != MAGIC) { - throw new IllegalArgumentException( - "Improper SigningCertificateLineage format: MAGIC header mismatch."); - } - return read(inputByteBuffer, inputByteBuffer.getInt()); - } - - private static SigningCertificateLineage read(ByteBuffer inputByteBuffer, int version) - throws IOException { - switch (version) { - case FIRST_VERSION: - try { - List nodes = - V3SigningCertificateLineage.readSigningCertificateLineage( - getLengthPrefixedSlice(inputByteBuffer)); - int minSdkVersion = calculateMinSdkVersion(nodes); - return new SigningCertificateLineage(minSdkVersion, nodes); - } catch (ApkFormatException e) { - // unable to get a proper length-prefixed lineage slice - throw new IOException("Unable to read list of signing certificate nodes in " - + "SigningCertificateLineage", e); - } - default: - throw new IllegalArgumentException( - "Improper SigningCertificateLineage format: unrecognized version."); - } - } - - private static int calculateMinSdkVersion(List nodes) { - if (nodes == null) { - throw new IllegalArgumentException("Can't calculate minimum SDK version of null nodes"); - } - int minSdkVersion = AndroidSdkVersion.P; // lineage introduced in P - for (SigningCertificateNode node : nodes) { - if (node.sigAlgorithm != null) { - int nodeMinSdkVersion = node.sigAlgorithm.getMinSdkVersion(); - if (nodeMinSdkVersion > minSdkVersion) { - minSdkVersion = nodeMinSdkVersion; - } - } - } - return minSdkVersion; - } - - private ByteBuffer write() { - byte[] encodedLineage = - V3SigningCertificateLineage.encodeSigningCertificateLineage(mSigningLineage); - int payloadSize = 4 + 4 + 4 + encodedLineage.length; - ByteBuffer result = ByteBuffer.allocate(payloadSize); - result.order(ByteOrder.LITTLE_ENDIAN); - result.putInt(MAGIC); - result.putInt(CURRENT_VERSION); - result.putInt(encodedLineage.length); - result.put(encodedLineage); - result.flip(); - return result; - } - - public byte[] encodeSigningCertificateLineage() { - return V3SigningCertificateLineage.encodeSigningCertificateLineage(mSigningLineage); - } - - public List sortSignerConfigs( - List signerConfigs) { - if (signerConfigs == null) { - throw new NullPointerException("signerConfigs == null"); - } - - // not the most elegant sort, but we expect signerConfigs to be quite small (1 or 2 signers - // in most cases) and likely already sorted, so not worth the overhead of doing anything - // fancier - List sortedSignerConfigs = - new ArrayList<>(signerConfigs.size()); - for (int i = 0; i < mSigningLineage.size(); i++) { - for (int j = 0; j < signerConfigs.size(); j++) { - DefaultApkSignerEngine.SignerConfig config = signerConfigs.get(j); - if (mSigningLineage.get(i).signingCert.equals(config.getCertificates().get(0))) { - sortedSignerConfigs.add(config); - break; - } - } - } - if (sortedSignerConfigs.size() != signerConfigs.size()) { - throw new IllegalArgumentException("SignerConfigs supplied which are not present in the" - + " SigningCertificateLineage"); - } - return sortedSignerConfigs; - } - - /** - * Returns the SignerCapabilities for the signer in the lineage that matches the provided - * config. - */ - public SignerCapabilities getSignerCapabilities(SignerConfig config) { - if (config == null) { - throw new NullPointerException("config == null"); - } - - X509Certificate cert = config.getCertificate(); - return getSignerCapabilities(cert); - } - - /** - * Returns the SignerCapabilities for the signer in the lineage that matches the provided - * certificate. - */ - public SignerCapabilities getSignerCapabilities(X509Certificate cert) { - if (cert == null) { - throw new NullPointerException("cert == null"); - } - - for (int i = 0; i < mSigningLineage.size(); i++) { - SigningCertificateNode lineageNode = mSigningLineage.get(i); - if (lineageNode.signingCert.equals(cert)) { - int flags = lineageNode.flags; - return new SignerCapabilities.Builder(flags).build(); - } - } - - // the provided signer certificate was not found in the lineage - throw new IllegalArgumentException("Certificate (" + cert.getSubjectDN() - + ") not found in the SigningCertificateLineage"); - } - - /** - * Updates the SignerCapabilities for the signer in the lineage that matches the provided - * config. Only those capabilities that have been modified through the setXX methods will be - * updated for the signer to prevent unset default values from being applied. - */ - public void updateSignerCapabilities(SignerConfig config, SignerCapabilities capabilities) { - if (config == null) { - throw new NullPointerException("config == null"); - } - - X509Certificate cert = config.getCertificate(); - for (int i = 0; i < mSigningLineage.size(); i++) { - SigningCertificateNode lineageNode = mSigningLineage.get(i); - if (lineageNode.signingCert.equals(cert)) { - int flags = lineageNode.flags; - SignerCapabilities newCapabilities = new SignerCapabilities.Builder( - flags).setCallerConfiguredCapabilities(capabilities).build(); - lineageNode.flags = newCapabilities.getFlags(); - return; - } - } - - // the provided signer config was not found in the lineage - throw new IllegalArgumentException("Certificate (" + cert.getSubjectDN() - + ") not found in the SigningCertificateLineage"); - } - - /** - * Returns a list containing all of the certificates in the lineage. - */ - public List getCertificatesInLineage() { - List certs = new ArrayList<>(); - for (int i = 0; i < mSigningLineage.size(); i++) { - X509Certificate cert = mSigningLineage.get(i).signingCert; - certs.add(cert); - } - return certs; - } - - /** - * Returns {@code true} if the specified config is in the lineage. - */ - public boolean isSignerInLineage(SignerConfig config) { - if (config == null) { - throw new NullPointerException("config == null"); - } - - X509Certificate cert = config.getCertificate(); - return isCertificateInLineage(cert); - } - - /** - * Returns {@code true} if the specified certificate is in the lineage. - */ - public boolean isCertificateInLineage(X509Certificate cert) { - if (cert == null) { - throw new NullPointerException("cert == null"); - } - - for (int i = 0; i < mSigningLineage.size(); i++) { - if (mSigningLineage.get(i).signingCert.equals(cert)) { - return true; - } - } - return false; - } - - private static int calculateDefaultFlags() { - return PAST_CERT_INSTALLED_DATA | PAST_CERT_PERMISSION - | PAST_CERT_SHARED_USER_ID | PAST_CERT_AUTH; - } - - /** - * Returns a new SigingCertificateLineage which terminates at the node corresponding to the - * given certificate. This is useful in the event of rotating to a new signing algorithm that - * is only supported on some platform versions. It enables a v3 signature to be generated using - * this signing certificate and the shortened proof-of-rotation record from this sub lineage in - * conjunction with the appropriate SDK version values. - * - * @param x509Certificate the signing certificate for which to search - * @return A new SigningCertificateLineage if the given certificate is present. - * - * @throws IllegalArgumentException if the provided certificate is not in the lineage. - */ - public SigningCertificateLineage getSubLineage(X509Certificate x509Certificate) { - if (x509Certificate == null) { - throw new NullPointerException("x509Certificate == null"); - } - for (int i = 0; i < mSigningLineage.size(); i++) { - if (mSigningLineage.get(i).signingCert.equals(x509Certificate)) { - return new SigningCertificateLineage( - mMinSdkVersion, new ArrayList<>(mSigningLineage.subList(0, i + 1))); - } - } - - // looks like we didn't find the cert, - throw new IllegalArgumentException("Certificate not found in SigningCertificateLineage"); - } - - /** - * Consolidates all of the lineages found in an APK into one lineage, which is the longest one. - * In so doing, it also checks that all of the smaller lineages are contained in the largest, - * and that they properly cover the desired platform ranges. - * - * An APK may contain multiple lineages, one for each signer, which correspond to different - * supported platform versions. In this event, the lineage(s) from the earlier platform - * version(s) need to be present in the most recent (longest) one to make sure that when a - * platform version changes. - * - * This does not verify that the largest lineage corresponds to the most recent supported - * platform version. That check requires is performed during v3 verification. - */ - public static SigningCertificateLineage consolidateLineages( - List lineages) { - if (lineages == null || lineages.isEmpty()) { - return null; - } - int largestIndex = 0; - int maxSize = 0; - - // determine the longest chain - for (int i = 0; i < lineages.size(); i++) { - int curSize = lineages.get(i).size(); - if (curSize > maxSize) { - largestIndex = i; - maxSize = curSize; - } - } - - List largestList = lineages.get(largestIndex).mSigningLineage; - // make sure all other lineages fit into this one, with the same capabilities - for (int i = 0; i < lineages.size(); i++) { - if (i == largestIndex) { - continue; - } - List underTest = lineages.get(i).mSigningLineage; - if (!underTest.equals(largestList.subList(0, underTest.size()))) { - throw new IllegalArgumentException("Inconsistent SigningCertificateLineages. " - + "Not all lineages are subsets of each other."); - } - } - - // if we've made it this far, they all check out, so just return the largest - return lineages.get(largestIndex); - } - - /** - * Representation of the capabilities the APK would like to grant to its old signing - * certificates. The {@code SigningCertificateLineage} provides two conceptual data structures. - * 1) proof of rotation - Evidence that other parties can trust an APK's current signing - * certificate if they trust an older one in this lineage - * 2) self-trust - certain capabilities may have been granted by an APK to other parties based - * on its own signing certificate. When it changes its signing certificate it may want to - * allow the other parties to retain those capabilities. - * {@code SignerCapabilties} provides a representation of the second structure. - * - *

Use {@link Builder} to obtain configuration instances. - */ - public static class SignerCapabilities { - private final int mFlags; - - private final int mCallerConfiguredFlags; - - private SignerCapabilities(int flags) { - this(flags, 0); - } - - private SignerCapabilities(int flags, int callerConfiguredFlags) { - mFlags = flags; - mCallerConfiguredFlags = callerConfiguredFlags; - } - - private int getFlags() { - return mFlags; - } - - /** - * Returns {@code true} if the capabilities of this object match those of the provided - * object. - */ - public boolean equals(SignerCapabilities other) { - return this.mFlags == other.mFlags; - } - - /** - * Returns {@code true} if this object has the installed data capability. - */ - public boolean hasInstalledData() { - return (mFlags & PAST_CERT_INSTALLED_DATA) != 0; - } - - /** - * Returns {@code true} if this object has the shared UID capability. - */ - public boolean hasSharedUid() { - return (mFlags & PAST_CERT_SHARED_USER_ID) != 0; - } - - /** - * Returns {@code true} if this object has the permission capability. - */ - public boolean hasPermission() { - return (mFlags & PAST_CERT_PERMISSION) != 0; - } - - /** - * Returns {@code true} if this object has the rollback capability. - */ - public boolean hasRollback() { - return (mFlags & PAST_CERT_ROLLBACK) != 0; - } - - /** - * Returns {@code true} if this object has the auth capability. - */ - public boolean hasAuth() { - return (mFlags & PAST_CERT_AUTH) != 0; - } - - /** - * Builder of {@link SignerCapabilities} instances. - */ - public static class Builder { - private int mFlags; - - private int mCallerConfiguredFlags; - - /** - * Constructs a new {@code Builder}. - */ - public Builder() { - mFlags = calculateDefaultFlags(); - } - - /** - * Constructs a new {@code Builder} with the initial capabilities set to the provided - * flags. - */ - public Builder(int flags) { - mFlags = flags; - } - - /** - * Set the {@code PAST_CERT_INSTALLED_DATA} flag in this capabilities object. This flag - * is used by the platform to determine if installed data associated with previous - * signing certificate should be trusted. In particular, this capability is required to - * perform signing certificate rotation during an upgrade on-device. Without it, the - * platform will not permit the app data from the old signing certificate to - * propagate to the new version. Typically, this flag should be set to enable signing - * certificate rotation, and may be unset later when the app developer is satisfied that - * their install base is as migrated as it will be. - */ - public Builder setInstalledData(boolean enabled) { - mCallerConfiguredFlags |= PAST_CERT_INSTALLED_DATA; - if (enabled) { - mFlags |= PAST_CERT_INSTALLED_DATA; - } else { - mFlags &= ~PAST_CERT_INSTALLED_DATA; - } - return this; - } - - /** - * Set the {@code PAST_CERT_SHARED_USER_ID} flag in this capabilities object. This flag - * is used by the platform to determine if this app is willing to be sharedUid with - * other apps which are still signed with the associated signing certificate. This is - * useful in situations where sharedUserId apps would like to change their signing - * certificate, but can't guarantee the order of updates to those apps. - */ - public Builder setSharedUid(boolean enabled) { - mCallerConfiguredFlags |= PAST_CERT_SHARED_USER_ID; - if (enabled) { - mFlags |= PAST_CERT_SHARED_USER_ID; - } else { - mFlags &= ~PAST_CERT_SHARED_USER_ID; - } - return this; - } - - /** - * Set the {@code PAST_CERT_PERMISSION} flag in this capabilities object. This flag - * is used by the platform to determine if this app is willing to grant SIGNATURE - * permissions to apps signed with the associated signing certificate. Without this - * capability, an application signed with the older certificate will not be granted the - * SIGNATURE permissions defined by this app. In addition, if multiple apps define the - * same SIGNATURE permission, the second one the platform sees will not be installable - * if this capability is not set and the signing certificates differ. - */ - public Builder setPermission(boolean enabled) { - mCallerConfiguredFlags |= PAST_CERT_PERMISSION; - if (enabled) { - mFlags |= PAST_CERT_PERMISSION; - } else { - mFlags &= ~PAST_CERT_PERMISSION; - } - return this; - } - - /** - * Set the {@code PAST_CERT_ROLLBACK} flag in this capabilities object. This flag - * is used by the platform to determine if this app is willing to upgrade to a new - * version that is signed by one of its past signing certificates. - * - * WARNING: this effectively removes any benefit of signing certificate changes, - * since a compromised key could retake control of an app even after change, and should - * only be used if there is a problem encountered when trying to ditch an older cert - * - */ - public Builder setRollback(boolean enabled) { - mCallerConfiguredFlags |= PAST_CERT_ROLLBACK; - if (enabled) { - mFlags |= PAST_CERT_ROLLBACK; - } else { - mFlags &= ~PAST_CERT_ROLLBACK; - } - return this; - } - - /** - * Set the {@code PAST_CERT_AUTH} flag in this capabilities object. This flag - * is used by the platform to determine whether or not privileged access based on - * authenticator module signing certificates should be granted. - */ - public Builder setAuth(boolean enabled) { - mCallerConfiguredFlags |= PAST_CERT_AUTH; - if (enabled) { - mFlags |= PAST_CERT_AUTH; - } else { - mFlags &= ~PAST_CERT_AUTH; - } - return this; - } - - /** - * Applies the capabilities that were explicitly set in the provided capabilities object - * to this builder. Any values that were not set will not be applied to this builder - * to prevent unintentinoally setting a capability back to a default value. - */ - public Builder setCallerConfiguredCapabilities(SignerCapabilities capabilities) { - // The mCallerConfiguredFlags should have a bit set for each capability that was - // set by a caller. If a capability was explicitly set then the corresponding bit - // in mCallerConfiguredFlags should be set. This allows the provided capabilities - // to take effect for those set by the caller while those that were not set will - // be cleared by the bitwise and and the initial value for the builder will remain. - mFlags = (mFlags & ~capabilities.mCallerConfiguredFlags) | - (capabilities.mFlags & capabilities.mCallerConfiguredFlags); - return this; - } - - /** - * Returns a new {@code SignerConfig} instance configured based on the configuration of - * this builder. - */ - public SignerCapabilities build() { - return new SignerCapabilities(mFlags, mCallerConfiguredFlags); - } - } - } - - /** - * Configuration of a signer. Used to add a new entry to the {@link SigningCertificateLineage} - * - *

Use {@link Builder} to obtain configuration instances. - */ - public static class SignerConfig { - private final PrivateKey mPrivateKey; - private final X509Certificate mCertificate; - - private SignerConfig( - PrivateKey privateKey, - X509Certificate certificate) { - mPrivateKey = privateKey; - mCertificate = certificate; - } - - /** - * Returns the signing key of this signer. - */ - public PrivateKey getPrivateKey() { - return mPrivateKey; - } - - /** - * Returns the certificate(s) of this signer. The first certificate's public key corresponds - * to this signer's private key. - */ - public X509Certificate getCertificate() { - return mCertificate; - } - - /** - * Builder of {@link SignerConfig} instances. - */ - public static class Builder { - private final PrivateKey mPrivateKey; - private final X509Certificate mCertificate; - - /** - * Constructs a new {@code Builder}. - * - * @param privateKey signing key - * @param certificate the X.509 certificate with a subject public key of the - * {@code privateKey}. - */ - public Builder( - PrivateKey privateKey, - X509Certificate certificate) { - mPrivateKey = privateKey; - mCertificate = certificate; - } - - /** - * Returns a new {@code SignerConfig} instance configured based on the configuration of - * this builder. - */ - public SignerConfig build() { - return new SignerConfig( - mPrivateKey, - mCertificate); - } - } - } - - /** - * Builder of {@link SigningCertificateLineage} instances. - */ - public static class Builder { - private final SignerConfig mOriginalSignerConfig; - private final SignerConfig mNewSignerConfig; - private SignerCapabilities mOriginalCapabilities; - private SignerCapabilities mNewCapabilities; - private int mMinSdkVersion; - /** - * Constructs a new {@code Builder}. - * - * @param originalSignerConfig first signer in this lineage, parent of the next - * @param newSignerConfig new signer in the lineage; the new signing key that the APK will - * use - */ - public Builder( - SignerConfig originalSignerConfig, - SignerConfig newSignerConfig) { - if (originalSignerConfig == null || newSignerConfig == null) { - throw new NullPointerException("Can't pass null SignerConfigs when constructing a " - + "new SigningCertificateLineage"); - } - mOriginalSignerConfig = originalSignerConfig; - mNewSignerConfig = newSignerConfig; - } - - /** - * Sets the minimum Android platform version (API Level) on which this lineage is expected - * to validate. It is possible that newer signers in the lineage may not be recognized on - * the given platform, but as long as an older signer is, the lineage can still be used to - * sign an APK for the given platform. - * - * By default, this value is set to the value for the - * P release, since this structure was created for that release, and will also be set to - * that value if a smaller one is specified. - */ - public Builder setMinSdkVersion(int minSdkVersion) { - mMinSdkVersion = minSdkVersion; - return this; - } - - /** - * Sets capabilities to give {@code mOriginalSignerConfig}. These capabilities allow an - * older signing certificate to still be used in some situations on the platform even though - * the APK is now being signed by a newer signing certificate. - */ - public Builder setOriginalCapabilities(SignerCapabilities signerCapabilities) { - if (signerCapabilities == null) { - throw new NullPointerException("signerCapabilities == null"); - } - mOriginalCapabilities = signerCapabilities; - return this; - } - - /** - * Sets capabilities to give {@code mNewSignerConfig}. These capabilities allow an - * older signing certificate to still be used in some situations on the platform even though - * the APK is now being signed by a newer signing certificate. By default, the new signer - * will have all capabilities, so when first switching to a new signing certificate, these - * capabilities have no effect, but they will act as the default level of trust when moving - * to a new signing certificate. - */ - public Builder setNewCapabilities(SignerCapabilities signerCapabilities) { - if (signerCapabilities == null) { - throw new NullPointerException("signerCapabilities == null"); - } - mNewCapabilities = signerCapabilities; - return this; - } - - public SigningCertificateLineage build() - throws CertificateEncodingException, InvalidKeyException, NoSuchAlgorithmException, - SignatureException { - if (mMinSdkVersion < AndroidSdkVersion.P) { - mMinSdkVersion = AndroidSdkVersion.P; - } - - if (mOriginalCapabilities == null) { - mOriginalCapabilities = new SignerCapabilities.Builder().build(); - } - - if (mNewCapabilities == null) { - mNewCapabilities = new SignerCapabilities.Builder().build(); - } - - return createSigningLineage( - mMinSdkVersion, mOriginalSignerConfig, mOriginalCapabilities, - mNewSignerConfig, mNewCapabilities); - } - } -} diff --git a/app/src/main/java/com/android/apksig/SourceStampVerifier.java b/app/src/main/java/com/android/apksig/SourceStampVerifier.java deleted file mode 100644 index 587cbd31fe..0000000000 --- a/app/src/main/java/com/android/apksig/SourceStampVerifier.java +++ /dev/null @@ -1,882 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig; - -import static com.android.apksig.Constants.VERSION_APK_SIGNATURE_SCHEME_V2; -import static com.android.apksig.Constants.VERSION_APK_SIGNATURE_SCHEME_V3; -import static com.android.apksig.Constants.VERSION_JAR_SIGNATURE_SCHEME; -import static com.android.apksig.apk.ApkUtilsLite.computeSha256DigestBytes; -import static com.android.apksig.internal.apk.stamp.SourceStampConstants.SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME; -import static com.android.apksig.internal.apk.v1.V1SchemeConstants.MANIFEST_ENTRY_NAME; - -import com.android.apksig.apk.ApkFormatException; -import com.android.apksig.apk.ApkUtilsLite; -import com.android.apksig.internal.apk.ApkSigResult; -import com.android.apksig.internal.apk.ApkSignerInfo; -import com.android.apksig.internal.apk.ApkSigningBlockUtilsLite; -import com.android.apksig.internal.apk.ContentDigestAlgorithm; -import com.android.apksig.internal.apk.SignatureAlgorithm; -import com.android.apksig.internal.apk.SignatureInfo; -import com.android.apksig.internal.apk.SignatureNotFoundException; -import com.android.apksig.internal.apk.stamp.SourceStampConstants; -import com.android.apksig.internal.apk.stamp.V2SourceStampVerifier; -import com.android.apksig.internal.apk.v2.V2SchemeConstants; -import com.android.apksig.internal.apk.v3.V3SchemeConstants; -import com.android.apksig.internal.util.AndroidSdkVersion; -import com.android.apksig.internal.util.GuaranteedEncodedFormX509Certificate; -import com.android.apksig.internal.zip.CentralDirectoryRecord; -import com.android.apksig.internal.zip.LocalFileRecord; -import com.android.apksig.internal.zip.ZipUtils; -import com.android.apksig.util.DataSource; -import com.android.apksig.util.DataSources; -import com.android.apksig.zip.ZipFormatException; -import com.android.apksig.zip.ZipSections; - -import java.io.ByteArrayInputStream; -import java.io.Closeable; -import java.io.File; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.BufferUnderflowException; -import java.nio.ByteBuffer; -import java.security.NoSuchAlgorithmException; -import java.security.cert.Certificate; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.EnumMap; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * APK source stamp verifier intended only to verify the validity of the stamp signature. - * - *

Note, this verifier does not validate the signatures of the jar signing / APK signature blocks - * when obtaining the digests for verification. This verifier should only be used in cases where - * another mechanism has already been used to verify the APK signatures. - */ -public class SourceStampVerifier { - private final File mApkFile; - private final DataSource mApkDataSource; - - private final int mMinSdkVersion; - private final int mMaxSdkVersion; - - private SourceStampVerifier( - File apkFile, - DataSource apkDataSource, - int minSdkVersion, - int maxSdkVersion) { - mApkFile = apkFile; - mApkDataSource = apkDataSource; - mMinSdkVersion = minSdkVersion; - mMaxSdkVersion = maxSdkVersion; - } - - /** - * Verifies the APK's source stamp signature and returns the result of the verification. - * - *

The APK's source stamp can be considered verified if the result's {@link - * Result#isVerified()} returns {@code true}. If source stamp verification fails all of the - * resulting errors can be obtained from {@link Result#getAllErrors()}, or individual errors - * can be obtained as follows: - *

    - *
  • Obtain the generic errors via {@link Result#getErrors()} - *
  • Obtain the V2 signers via {@link Result#getV2SchemeSigners()}, then for each signer - * query for any errors with {@link Result.SignerInfo#getErrors()} - *
  • Obtain the V3 signers via {@link Result#getV3SchemeSigners()}, then for each signer - * query for any errors with {@link Result.SignerInfo#getErrors()} - *
  • Obtain the source stamp signer via {@link Result#getSourceStampInfo()}, then query - * for any stamp errors with {@link Result.SourceStampInfo#getErrors()} - *
- */ - public SourceStampVerifier.Result verifySourceStamp() { - return verifySourceStamp(null); - } - - /** - * Verifies the APK's source stamp signature, including verification that the SHA-256 digest of - * the stamp signing certificate matches the {@code expectedCertDigest}, and returns the result - * of the verification. - * - *

A value of {@code null} for the {@code expectedCertDigest} will verify the source stamp, - * if present, without verifying the actual source stamp certificate used to sign the source - * stamp. This can be used to verify an APK contains a properly signed source stamp without - * verifying a particular signer. - * - * @see #verifySourceStamp() - */ - public SourceStampVerifier.Result verifySourceStamp(String expectedCertDigest) { - Closeable in = null; - try { - DataSource apk; - if (mApkDataSource != null) { - apk = mApkDataSource; - } else if (mApkFile != null) { - RandomAccessFile f = new RandomAccessFile(mApkFile, "r"); - in = f; - apk = DataSources.asDataSource(f, 0, f.length()); - } else { - throw new IllegalStateException("APK not provided"); - } - return verifySourceStamp(apk, expectedCertDigest); - } catch (IOException e) { - Result result = new Result(); - result.addVerificationError(ApkVerificationIssue.UNEXPECTED_EXCEPTION, e); - return result; - } finally { - if (in != null) { - try { - in.close(); - } catch (IOException ignored) { - } - } - } - } - - /** - * Verifies the provided {@code apk}'s source stamp signature, including verification of the - * SHA-256 digest of the stamp signing certificate matches the {@code expectedCertDigest}, and - * returns the result of the verification. - * - * @see #verifySourceStamp(String) - */ - private SourceStampVerifier.Result verifySourceStamp(DataSource apk, - String expectedCertDigest) { - Result result = new Result(); - try { - ZipSections zipSections = ApkUtilsLite.findZipSections(apk); - // Attempt to obtain the source stamp's certificate digest from the APK. - List cdRecords = - ZipUtils.parseZipCentralDirectory(apk, zipSections); - CentralDirectoryRecord sourceStampCdRecord = null; - for (CentralDirectoryRecord cdRecord : cdRecords) { - if (SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME.equals(cdRecord.getName())) { - sourceStampCdRecord = cdRecord; - break; - } - } - - // If the source stamp's certificate digest is not available within the APK then the - // source stamp cannot be verified; check if a source stamp signing block is in the - // APK's signature block to determine the appropriate status to return. - if (sourceStampCdRecord == null) { - boolean stampSigningBlockFound; - try { - ApkSigningBlockUtilsLite.findSignature(apk, zipSections, - SourceStampConstants.V2_SOURCE_STAMP_BLOCK_ID); - stampSigningBlockFound = true; - } catch (SignatureNotFoundException e) { - stampSigningBlockFound = false; - } - result.addVerificationError(stampSigningBlockFound - ? ApkVerificationIssue.SOURCE_STAMP_SIGNATURE_BLOCK_WITHOUT_CERT_DIGEST - : ApkVerificationIssue.SOURCE_STAMP_CERT_DIGEST_AND_SIG_BLOCK_MISSING); - return result; - } - - // Verify that the contents of the source stamp certificate digest match the expected - // value, if provided. - byte[] sourceStampCertificateDigest = - LocalFileRecord.getUncompressedData( - apk, - sourceStampCdRecord, - zipSections.getZipCentralDirectoryOffset()); - if (expectedCertDigest != null) { - String actualCertDigest = ApkSigningBlockUtilsLite.toHex( - sourceStampCertificateDigest); - if (!expectedCertDigest.equalsIgnoreCase(actualCertDigest)) { - result.addVerificationError( - ApkVerificationIssue.SOURCE_STAMP_EXPECTED_DIGEST_MISMATCH, - actualCertDigest, expectedCertDigest); - return result; - } - } - - Map> signatureSchemeApkContentDigests = - new HashMap<>(); - if (mMaxSdkVersion >= AndroidSdkVersion.P) { - SignatureInfo signatureInfo; - try { - signatureInfo = ApkSigningBlockUtilsLite.findSignature(apk, zipSections, - V3SchemeConstants.APK_SIGNATURE_SCHEME_V3_BLOCK_ID); - } catch (SignatureNotFoundException e) { - signatureInfo = null; - } - if (signatureInfo != null) { - Map apkContentDigests = new EnumMap<>( - ContentDigestAlgorithm.class); - parseSigners(signatureInfo.signatureBlock, VERSION_APK_SIGNATURE_SCHEME_V3, - apkContentDigests, result); - signatureSchemeApkContentDigests.put( - VERSION_APK_SIGNATURE_SCHEME_V3, apkContentDigests); - } - } - - if (mMaxSdkVersion >= AndroidSdkVersion.N && (mMinSdkVersion < AndroidSdkVersion.P || - signatureSchemeApkContentDigests.isEmpty())) { - SignatureInfo signatureInfo; - try { - signatureInfo = ApkSigningBlockUtilsLite.findSignature(apk, zipSections, - V2SchemeConstants.APK_SIGNATURE_SCHEME_V2_BLOCK_ID); - } catch (SignatureNotFoundException e) { - signatureInfo = null; - } - if (signatureInfo != null) { - Map apkContentDigests = new EnumMap<>( - ContentDigestAlgorithm.class); - parseSigners(signatureInfo.signatureBlock, VERSION_APK_SIGNATURE_SCHEME_V2, - apkContentDigests, result); - signatureSchemeApkContentDigests.put( - VERSION_APK_SIGNATURE_SCHEME_V2, apkContentDigests); - } - } - - if (mMinSdkVersion < AndroidSdkVersion.N - || signatureSchemeApkContentDigests.isEmpty()) { - Map apkContentDigests = - getApkContentDigestFromV1SigningScheme(cdRecords, apk, zipSections, result); - signatureSchemeApkContentDigests.put(VERSION_JAR_SIGNATURE_SCHEME, - apkContentDigests); - } - - ApkSigResult sourceStampResult = - V2SourceStampVerifier.verify( - apk, - zipSections, - sourceStampCertificateDigest, - signatureSchemeApkContentDigests, - mMinSdkVersion, - mMaxSdkVersion); - result.mergeFrom(sourceStampResult); - return result; - } catch (ApkFormatException | IOException | ZipFormatException e) { - result.addVerificationError(ApkVerificationIssue.MALFORMED_APK, e); - } catch (NoSuchAlgorithmException e) { - result.addVerificationError(ApkVerificationIssue.UNEXPECTED_EXCEPTION, e); - } catch (SignatureNotFoundException e) { - result.addVerificationError(ApkVerificationIssue.SOURCE_STAMP_SIG_MISSING); - } - return result; - } - - /** - * Parses each signer in the provided APK V2 / V3 signature block and populates corresponding - * {@code SignerInfo} of the provided {@code result} and their {@code apkContentDigests}. - * - *

This method adds one or more errors to the {@code result} if a verification error is - * expected to be encountered on an Android platform version in the - * {@code [minSdkVersion, maxSdkVersion]} range. - */ - public static void parseSigners( - ByteBuffer apkSignatureSchemeBlock, - int apkSigSchemeVersion, - Map apkContentDigests, - Result result) { - boolean isV2Block = apkSigSchemeVersion == VERSION_APK_SIGNATURE_SCHEME_V2; - // Both the V2 and V3 signature blocks contain the following: - // * length-prefixed sequence of length-prefixed signers - ByteBuffer signers; - try { - signers = ApkSigningBlockUtilsLite.getLengthPrefixedSlice(apkSignatureSchemeBlock); - } catch (ApkFormatException e) { - result.addVerificationWarning(isV2Block ? ApkVerificationIssue.V2_SIG_MALFORMED_SIGNERS - : ApkVerificationIssue.V3_SIG_MALFORMED_SIGNERS); - return; - } - if (!signers.hasRemaining()) { - result.addVerificationWarning(isV2Block ? ApkVerificationIssue.V2_SIG_NO_SIGNERS - : ApkVerificationIssue.V3_SIG_NO_SIGNERS); - return; - } - - CertificateFactory certFactory; - try { - certFactory = CertificateFactory.getInstance("X.509"); - } catch (CertificateException e) { - throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e); - } - while (signers.hasRemaining()) { - Result.SignerInfo signerInfo = new Result.SignerInfo(); - if (isV2Block) { - result.addV2Signer(signerInfo); - } else { - result.addV3Signer(signerInfo); - } - try { - ByteBuffer signer = ApkSigningBlockUtilsLite.getLengthPrefixedSlice(signers); - parseSigner( - signer, - apkSigSchemeVersion, - certFactory, - apkContentDigests, - signerInfo); - } catch (ApkFormatException | BufferUnderflowException e) { - signerInfo.addVerificationWarning( - isV2Block ? ApkVerificationIssue.V2_SIG_MALFORMED_SIGNER - : ApkVerificationIssue.V3_SIG_MALFORMED_SIGNER); - return; - } - } - } - - /** - * Parses the provided signer block and populates the {@code result}. - * - *

This verifies signatures over {@code signed-data} contained in this block but does not - * verify the integrity of the rest of the APK. To facilitate APK integrity verification, this - * method adds the {@code contentDigestsToVerify}. These digests can then be used to verify the - * integrity of the APK. - * - *

This method adds one or more errors to the {@code result} if a verification error is - * expected to be encountered on an Android platform version in the - * {@code [minSdkVersion, maxSdkVersion]} range. - */ - private static void parseSigner( - ByteBuffer signerBlock, - int apkSigSchemeVersion, - CertificateFactory certFactory, - Map apkContentDigests, - Result.SignerInfo signerInfo) - throws ApkFormatException { - boolean isV2Signer = apkSigSchemeVersion == VERSION_APK_SIGNATURE_SCHEME_V2; - // Both the V2 and V3 signer blocks contain the following: - // * length-prefixed signed data - // * length-prefixed sequence of length-prefixed digests: - // * uint32: signature algorithm ID - // * length-prefixed bytes: digest of contents - // * length-prefixed sequence of certificates: - // * length-prefixed bytes: X.509 certificate (ASN.1 DER encoded). - ByteBuffer signedData = ApkSigningBlockUtilsLite.getLengthPrefixedSlice(signerBlock); - ByteBuffer digests = ApkSigningBlockUtilsLite.getLengthPrefixedSlice(signedData); - ByteBuffer certificates = ApkSigningBlockUtilsLite.getLengthPrefixedSlice(signedData); - - // Parse the digests block - while (digests.hasRemaining()) { - try { - ByteBuffer digest = ApkSigningBlockUtilsLite.getLengthPrefixedSlice(digests); - int sigAlgorithmId = digest.getInt(); - byte[] digestBytes = ApkSigningBlockUtilsLite.readLengthPrefixedByteArray(digest); - SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.findById(sigAlgorithmId); - if (signatureAlgorithm == null) { - continue; - } - apkContentDigests.put(signatureAlgorithm.getContentDigestAlgorithm(), digestBytes); - } catch (ApkFormatException | BufferUnderflowException e) { - signerInfo.addVerificationWarning( - isV2Signer ? ApkVerificationIssue.V2_SIG_MALFORMED_DIGEST - : ApkVerificationIssue.V3_SIG_MALFORMED_DIGEST); - return; - } - } - - // Parse the certificates block - if (certificates.hasRemaining()) { - byte[] encodedCert = ApkSigningBlockUtilsLite.readLengthPrefixedByteArray(certificates); - X509Certificate certificate; - try { - certificate = (X509Certificate) certFactory.generateCertificate( - new ByteArrayInputStream(encodedCert)); - } catch (CertificateException e) { - signerInfo.addVerificationWarning( - isV2Signer ? ApkVerificationIssue.V2_SIG_MALFORMED_CERTIFICATE - : ApkVerificationIssue.V3_SIG_MALFORMED_CERTIFICATE); - return; - } - // Wrap the cert so that the result's getEncoded returns exactly the original encoded - // form. Without this, getEncoded may return a different form from what was stored in - // the signature. This is because some X509Certificate(Factory) implementations - // re-encode certificates. - certificate = new GuaranteedEncodedFormX509Certificate(certificate, encodedCert); - signerInfo.setSigningCertificate(certificate); - } - - if (signerInfo.getSigningCertificate() == null) { - signerInfo.addVerificationWarning( - isV2Signer ? ApkVerificationIssue.V2_SIG_NO_CERTIFICATES - : ApkVerificationIssue.V3_SIG_NO_CERTIFICATES); - return; - } - } - - /** - * Returns a mapping of the {@link ContentDigestAlgorithm} to the {@code byte[]} digest of the - * V1 / jar signing META-INF/MANIFEST.MF; if this file is not found then an empty {@code Map} is - * returned. - * - *

If any errors are encountered while parsing the V1 signers the provided {@code result} - * will be updated to include a warning, but the source stamp verification can still proceed. - */ - private static Map getApkContentDigestFromV1SigningScheme( - List cdRecords, - DataSource apk, - ZipSections zipSections, - Result result) - throws IOException, ApkFormatException { - CentralDirectoryRecord manifestCdRecord = null; - List signatureBlockRecords = new ArrayList<>(1); - Map v1ContentDigest = new EnumMap<>( - ContentDigestAlgorithm.class); - for (CentralDirectoryRecord cdRecord : cdRecords) { - String cdRecordName = cdRecord.getName(); - if (cdRecordName == null) { - continue; - } - if (manifestCdRecord == null && MANIFEST_ENTRY_NAME.equals(cdRecordName)) { - manifestCdRecord = cdRecord; - continue; - } - if (cdRecordName.startsWith("META-INF/") - && (cdRecordName.endsWith(".RSA") - || cdRecordName.endsWith(".DSA") - || cdRecordName.endsWith(".EC"))) { - signatureBlockRecords.add(cdRecord); - } - } - if (manifestCdRecord == null) { - // No JAR signing manifest file found. For SourceStamp verification, returning an empty - // digest is enough since this would affect the final digest signed by the stamp, and - // thus an empty digest will invalidate that signature. - return v1ContentDigest; - } - if (signatureBlockRecords.isEmpty()) { - result.addVerificationWarning(ApkVerificationIssue.JAR_SIG_NO_SIGNATURES); - } else { - for (CentralDirectoryRecord signatureBlockRecord : signatureBlockRecords) { - try { - CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); - byte[] signatureBlockBytes = LocalFileRecord.getUncompressedData(apk, - signatureBlockRecord, zipSections.getZipCentralDirectoryOffset()); - for (Certificate certificate : certFactory.generateCertificates( - new ByteArrayInputStream(signatureBlockBytes))) { - // If multiple certificates are found within the signature block only the - // first is used as the signer of this block. - if (certificate instanceof X509Certificate) { - Result.SignerInfo signerInfo = new Result.SignerInfo(); - signerInfo.setSigningCertificate((X509Certificate) certificate); - result.addV1Signer(signerInfo); - break; - } - } - } catch (CertificateException e) { - // Log a warning for the parsing exception but still proceed with the stamp - // verification. - result.addVerificationWarning(ApkVerificationIssue.JAR_SIG_PARSE_EXCEPTION, - signatureBlockRecord.getName(), e); - break; - } catch (ZipFormatException e) { - throw new ApkFormatException("Failed to read APK", e); - } - } - } - try { - byte[] manifestBytes = - LocalFileRecord.getUncompressedData( - apk, manifestCdRecord, zipSections.getZipCentralDirectoryOffset()); - v1ContentDigest.put( - ContentDigestAlgorithm.SHA256, computeSha256DigestBytes(manifestBytes)); - return v1ContentDigest; - } catch (ZipFormatException e) { - throw new ApkFormatException("Failed to read APK", e); - } - } - - /** - * Result of verifying the APK's source stamp signature; this signature can only be considered - * verified if {@link #isVerified()} returns true. - */ - public static class Result { - private final List mV1SchemeSigners = new ArrayList<>(); - private final List mV2SchemeSigners = new ArrayList<>(); - private final List mV3SchemeSigners = new ArrayList<>(); - private final List> mAllSchemeSigners = Arrays.asList(mV1SchemeSigners, - mV2SchemeSigners, mV3SchemeSigners); - private SourceStampInfo mSourceStampInfo; - - private final List mErrors = new ArrayList<>(); - private final List mWarnings = new ArrayList<>(); - - private boolean mVerified; - - void addVerificationError(int errorId, Object... params) { - mErrors.add(new ApkVerificationIssue(errorId, params)); - } - - void addVerificationWarning(int warningId, Object... params) { - mWarnings.add(new ApkVerificationIssue(warningId, params)); - } - - private void addV1Signer(SignerInfo signerInfo) { - mV1SchemeSigners.add(signerInfo); - } - - private void addV2Signer(SignerInfo signerInfo) { - mV2SchemeSigners.add(signerInfo); - } - - private void addV3Signer(SignerInfo signerInfo) { - mV3SchemeSigners.add(signerInfo); - } - - /** - * Returns {@code true} if the APK's source stamp signature - */ - public boolean isVerified() { - return mVerified; - } - - private void mergeFrom(ApkSigResult source) { - switch (source.signatureSchemeVersion) { - case Constants.VERSION_SOURCE_STAMP: - mVerified = source.verified; - if (!source.mSigners.isEmpty()) { - mSourceStampInfo = new SourceStampInfo(source.mSigners.get(0)); - } - break; - default: - throw new IllegalArgumentException( - "Unknown ApkSigResult Signing Block Scheme Id " - + source.signatureSchemeVersion); - } - } - - /** - * Returns a {@code List} of {@link SignerInfo} objects representing the V1 signers of the - * provided APK. - */ - public List getV1SchemeSigners() { - return mV1SchemeSigners; - } - - /** - * Returns a {@code List} of {@link SignerInfo} objects representing the V2 signers of the - * provided APK. - */ - public List getV2SchemeSigners() { - return mV2SchemeSigners; - } - - /** - * Returns a {@code List} of {@link SignerInfo} objects representing the V3 signers of the - * provided APK. - */ - public List getV3SchemeSigners() { - return mV3SchemeSigners; - } - - /** - * Returns the {@link SourceStampInfo} instance representing the source stamp signer for the - * APK, or null if the source stamp signature verification failed before the stamp signature - * block could be fully parsed. - */ - public SourceStampInfo getSourceStampInfo() { - return mSourceStampInfo; - } - - /** - * 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; - } - for (List signers : mAllSchemeSigners) { - for (SignerInfo signer : signers) { - if (signer.containsErrors()) { - return true; - } - } - } - if (mSourceStampInfo != null) { - if (mSourceStampInfo.containsErrors()) { - return true; - } - } - return false; - } - - /** - * Returns the errors encountered while verifying the APK's source stamp. - */ - public List getErrors() { - return mErrors; - } - - /** - * Returns the warnings encountered while verifying the APK's source stamp. - */ - public List getWarnings() { - return mWarnings; - } - - /** - * Returns all errors for this result, including any errors from signature scheme signers - * and the source stamp. - */ - public List getAllErrors() { - List errors = new ArrayList<>(); - errors.addAll(mErrors); - - for (List signers : mAllSchemeSigners) { - for (SignerInfo signer : signers) { - errors.addAll(signer.getErrors()); - } - } - if (mSourceStampInfo != null) { - errors.addAll(mSourceStampInfo.getErrors()); - } - return errors; - } - - /** - * Returns all warnings for this result, including any warnings from signature scheme - * signers and the source stamp. - */ - public List getAllWarnings() { - List warnings = new ArrayList<>(); - warnings.addAll(mWarnings); - - for (List signers : mAllSchemeSigners) { - for (SignerInfo signer : signers) { - warnings.addAll(signer.getWarnings()); - } - } - if (mSourceStampInfo != null) { - warnings.addAll(mSourceStampInfo.getWarnings()); - } - return warnings; - } - - /** - * Contains information about an APK's signer and any errors encountered while parsing the - * corresponding signature block. - */ - public static class SignerInfo { - private X509Certificate mSigningCertificate; - private final List mErrors = new ArrayList<>(); - private final List mWarnings = new ArrayList<>(); - - void setSigningCertificate(X509Certificate signingCertificate) { - mSigningCertificate = signingCertificate; - } - - void addVerificationError(int errorId, Object... params) { - mErrors.add(new ApkVerificationIssue(errorId, params)); - } - - void addVerificationWarning(int warningId, Object... params) { - mWarnings.add(new ApkVerificationIssue(warningId, params)); - } - - /** - * Returns the current signing certificate used by this signer. - */ - public X509Certificate getSigningCertificate() { - return mSigningCertificate; - } - - /** - * Returns a {@link List} of {@link ApkVerificationIssue} objects representing errors - * encountered during processing of this signer's signature block. - */ - public List getErrors() { - return mErrors; - } - - /** - * Returns a {@link List} of {@link ApkVerificationIssue} objects representing warnings - * encountered during processing of this signer's signature block. - */ - public List getWarnings() { - return mWarnings; - } - - /** - * Returns {@code true} if any errors were encountered while parsing this signer's - * signature block. - */ - public boolean containsErrors() { - return !mErrors.isEmpty(); - } - } - - /** - * Contains information about an APK's source stamp and any errors encountered while - * parsing the stamp signature block. - */ - public static class SourceStampInfo { - private final List mCertificates; - private final List mCertificateLineage; - - private final List mErrors = new ArrayList<>(); - private final List mWarnings = new ArrayList<>(); - - /* - * Since this utility is intended just to verify the source stamp, and the source stamp - * currently only logs warnings to prevent failing the APK signature verification, treat - * all warnings as errors. If the stamp verification is updated to log errors this - * should be set to false to ensure only errors trigger a failure verifying the source - * stamp. - */ - private static final boolean mWarningsAsErrors = true; - - private SourceStampInfo(ApkSignerInfo result) { - mCertificates = result.certs; - mCertificateLineage = result.certificateLineage; - mErrors.addAll(result.getErrors()); - mWarnings.addAll(result.getWarnings()); - } - - /** - * Returns the SourceStamp's signing certificate or {@code null} if not available. The - * certificate is guaranteed to be available if no errors were encountered during - * verification (see {@link #containsErrors()}. - * - *

This certificate contains the SourceStamp's public key. - */ - public X509Certificate getCertificate() { - return mCertificates.isEmpty() ? null : mCertificates.get(0); - } - - /** - * Returns a {@code List} of {@link X509Certificate} instances representing the source - * stamp signer's lineage with the oldest signer at element 0, or an empty {@code List} - * if the stamp's signing certificate has not been rotated. - */ - public List getCertificatesInLineage() { - return mCertificateLineage; - } - - /** - * Returns whether any errors were encountered during the source stamp verification. - */ - public boolean containsErrors() { - return !mErrors.isEmpty() || (mWarningsAsErrors && !mWarnings.isEmpty()); - } - - /** - * Returns a {@code List} of {@link ApkVerificationIssue} representing errors that were - * encountered during source stamp verification. - */ - public List getErrors() { - if (!mWarningsAsErrors) { - return mErrors; - } - List result = new ArrayList<>(); - result.addAll(mErrors); - result.addAll(mWarnings); - return result; - } - - /** - * Returns a {@code List} of {@link ApkVerificationIssue} representing warnings that - * were encountered during source stamp verification. - */ - public List getWarnings() { - return mWarnings; - } - } - } - - /** - * Builder of {@link SourceStampVerifier} instances. - * - *

The resulting verifier, by default, checks whether the APK's source stamp signature will - * verify on all platform versions. The APK's {@code android:minSdkVersion} attribute is not - * queried to determine the APK's minimum supported level, so the caller should specify a lower - * bound with {@link #setMinCheckedPlatformVersion(int)}. - */ - 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 source stamp verification of the provided {@code - * apk}. - */ - public Builder(File apk) { - if (apk == null) { - throw new NullPointerException("apk == null"); - } - mApkFile = apk; - mApkDataSource = null; - } - - /** - * Constructs a new {@code Builder} for source stamp verification of the provided {@code - * 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's source stamp is verified. - * - *

APK source stamp verification will confirm that the APK's stamp is expected to verify - * on all Android platforms starting from the platform version with the provided {@code - * minSdkVersion}. The upper end of the platform versions range can be modified via - * {@link #setMaxCheckedPlatformVersion(int)}. - * - * @param minSdkVersion API Level of the oldest platform for which to verify the APK - */ - public SourceStampVerifier.Builder setMinCheckedPlatformVersion(int minSdkVersion) { - mMinSdkVersion = minSdkVersion; - return this; - } - - /** - * Sets the newest Android platform version for which the APK's source stamp is verified. - * - *

APK source stamp verification will confirm that the APK's stamp is expected to verify - * on all platform versions up to and including the proviced {@code maxSdkVersion}. The - * lower end of the platform versions range can be modified via {@link - * #setMinCheckedPlatformVersion(int)}. - * - * @param maxSdkVersion API Level of the newest platform for which to verify the APK - * @see #setMinCheckedPlatformVersion(int) - */ - public SourceStampVerifier.Builder setMaxCheckedPlatformVersion(int maxSdkVersion) { - mMaxSdkVersion = maxSdkVersion; - return this; - } - - /** - * Returns a {@link SourceStampVerifier} initialized according to the configuration of this - * builder. - */ - public SourceStampVerifier build() { - return new SourceStampVerifier( - mApkFile, - mApkDataSource, - mMinSdkVersion, - mMaxSdkVersion); - } - } -} diff --git a/app/src/main/java/com/android/apksig/apk/ApkFormatException.java b/app/src/main/java/com/android/apksig/apk/ApkFormatException.java deleted file mode 100644 index a780134498..0000000000 --- a/app/src/main/java/com/android/apksig/apk/ApkFormatException.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.apk; - -/** - * Indicates that an APK is not well-formed. For example, this may indicate that the APK is not a - * well-formed ZIP archive, in which case {@link #getCause()} will return a - * {@link com.android.apksig.zip.ZipFormatException ZipFormatException}, or that the APK contains - * multiple ZIP entries with the same name. - */ -public class ApkFormatException extends Exception { - private static final long serialVersionUID = 1L; - - public ApkFormatException(String message) { - super(message); - } - - public ApkFormatException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/app/src/main/java/com/android/apksig/apk/ApkSigningBlockNotFoundException.java b/app/src/main/java/com/android/apksig/apk/ApkSigningBlockNotFoundException.java deleted file mode 100644 index fd961d5716..0000000000 --- a/app/src/main/java/com/android/apksig/apk/ApkSigningBlockNotFoundException.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.apk; - -/** - * Indicates that no APK Signing Block was found in an APK. - */ -public class ApkSigningBlockNotFoundException extends Exception { - private static final long serialVersionUID = 1L; - - public ApkSigningBlockNotFoundException(String message) { - super(message); - } - - public ApkSigningBlockNotFoundException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/app/src/main/java/com/android/apksig/apk/ApkUtils.java b/app/src/main/java/com/android/apksig/apk/ApkUtils.java deleted file mode 100644 index 156ea17c00..0000000000 --- a/app/src/main/java/com/android/apksig/apk/ApkUtils.java +++ /dev/null @@ -1,670 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.apk; - -import com.android.apksig.internal.apk.AndroidBinXmlParser; -import com.android.apksig.internal.apk.stamp.SourceStampConstants; -import com.android.apksig.internal.apk.v1.V1SchemeVerifier; -import com.android.apksig.internal.util.Pair; -import com.android.apksig.internal.zip.CentralDirectoryRecord; -import com.android.apksig.internal.zip.LocalFileRecord; -import com.android.apksig.internal.zip.ZipUtils; -import com.android.apksig.util.DataSource; -import com.android.apksig.zip.ZipFormatException; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; - -/** - * APK utilities. - */ -public abstract class ApkUtils { - - /** - * Name of the Android manifest ZIP entry in APKs. - */ - public static final String ANDROID_MANIFEST_ZIP_ENTRY_NAME = "AndroidManifest.xml"; - - /** Name of the SourceStamp certificate hash ZIP entry in APKs. */ - public static final String SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME = - SourceStampConstants.SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME; - - 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 { - com.android.apksig.zip.ZipSections zipSections = ApkUtilsLite.findZipSections(apk); - return new ZipSections( - zipSections.getZipCentralDirectoryOffset(), - zipSections.getZipCentralDirectorySizeBytes(), - zipSections.getZipCentralDirectoryRecordCount(), - zipSections.getZipEndOfCentralDirectoryOffset(), - zipSections.getZipEndOfCentralDirectory()); - } - - /** - * Information about the ZIP sections of an APK. - */ - public static class ZipSections extends com.android.apksig.zip.ZipSections { - public ZipSections( - long centralDirectoryOffset, - long centralDirectorySizeBytes, - int centralDirectoryRecordCount, - long eocdOffset, - ByteBuffer eocd) { - super(centralDirectoryOffset, centralDirectorySizeBytes, centralDirectoryRecordCount, - eocdOffset, eocd); - } - } - - /** - * 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); - } - - /** - * Updates the length of EOCD comment. - * - * @param zipEndOfCentralDirectory APK's ZIP End of Central Directory record - */ - public static void updateZipEocdCommentLen(ByteBuffer zipEndOfCentralDirectory) { - ByteBuffer eocd = zipEndOfCentralDirectory.slice(); - eocd.order(ByteOrder.LITTLE_ENDIAN); - ZipUtils.updateZipEocdCommentLen(eocd); - } - - /** - * Returns the APK Signing Block of the provided {@code apk}. - * - * @throws ApkFormatException if the APK is not a valid ZIP archive - * @throws IOException if an I/O error occurs - * @throws ApkSigningBlockNotFoundException if there is no APK Signing Block in the APK - * - * @see APK Signature Scheme v2 - * - */ - public static ApkSigningBlock findApkSigningBlock(DataSource apk) - throws ApkFormatException, IOException, ApkSigningBlockNotFoundException { - ApkUtils.ZipSections inputZipSections; - try { - inputZipSections = ApkUtils.findZipSections(apk); - } catch (ZipFormatException e) { - throw new ApkFormatException("Malformed APK: not a ZIP archive", e); - } - return findApkSigningBlock(apk, inputZipSections); - } - - /** - * Returns the APK Signing Block of the provided APK. - * - * @throws IOException if an I/O error occurs - * @throws ApkSigningBlockNotFoundException if there is no APK Signing Block in the APK - * - * @see APK Signature Scheme v2 - * - */ - public static ApkSigningBlock findApkSigningBlock(DataSource apk, ZipSections zipSections) - throws IOException, ApkSigningBlockNotFoundException { - ApkUtilsLite.ApkSigningBlock apkSigningBlock = ApkUtilsLite.findApkSigningBlock(apk, - zipSections); - return new ApkSigningBlock(apkSigningBlock.getStartOffset(), apkSigningBlock.getContents()); - } - - /** - * Information about the location of the APK Signing Block inside an APK. - */ - public static class ApkSigningBlock extends ApkUtilsLite.ApkSigningBlock { - /** - * Constructs a new {@code ApkSigningBlock}. - * - * @param startOffsetInApk start offset (in bytes, relative to start of file) of the APK - * Signing Block inside the APK file - * @param contents contents of the APK Signing Block - */ - public ApkSigningBlock(long startOffsetInApk, DataSource contents) { - super(startOffsetInApk, contents); - } - } - - /** - * Returns the contents of the APK's {@code AndroidManifest.xml}. - * - * @throws IOException if an I/O error occurs while reading the APK - * @throws ApkFormatException if the APK is malformed - */ - public static ByteBuffer getAndroidManifest(DataSource apk) - throws IOException, ApkFormatException { - ZipSections zipSections; - try { - zipSections = findZipSections(apk); - } catch (ZipFormatException e) { - throw new ApkFormatException("Not a valid ZIP archive", e); - } - List cdRecords = - V1SchemeVerifier.parseZipCentralDirectory(apk, zipSections); - CentralDirectoryRecord androidManifestCdRecord = null; - for (CentralDirectoryRecord cdRecord : cdRecords) { - if (ANDROID_MANIFEST_ZIP_ENTRY_NAME.equals(cdRecord.getName())) { - androidManifestCdRecord = cdRecord; - break; - } - } - if (androidManifestCdRecord == null) { - throw new ApkFormatException("Missing " + ANDROID_MANIFEST_ZIP_ENTRY_NAME); - } - DataSource lfhSection = apk.slice(0, zipSections.getZipCentralDirectoryOffset()); - - try { - return ByteBuffer.wrap( - LocalFileRecord.getUncompressedData( - lfhSection, androidManifestCdRecord, lfhSection.size())); - } catch (ZipFormatException e) { - throw new ApkFormatException("Failed to read " + ANDROID_MANIFEST_ZIP_ENTRY_NAME, e); - } - } - - /** - * Android resource ID of the {@code android:minSdkVersion} attribute in AndroidManifest.xml. - */ - private static final int MIN_SDK_VERSION_ATTR_ID = 0x0101020c; - - /** - * Android resource ID of the {@code android:debuggable} attribute in AndroidManifest.xml. - */ - private static final int DEBUGGABLE_ATTR_ID = 0x0101000f; - - /** - * Android resource ID of the {@code android:targetSandboxVersion} attribute in - * AndroidManifest.xml. - */ - private static final int TARGET_SANDBOX_VERSION_ATTR_ID = 0x0101054c; - - /** - * Android resource ID of the {@code android:targetSdkVersion} attribute in - * AndroidManifest.xml. - */ - private static final int TARGET_SDK_VERSION_ATTR_ID = 0x01010270; - private static final String USES_SDK_ELEMENT_TAG = "uses-sdk"; - - /** - * Android resource ID of the {@code android:versionCode} attribute in AndroidManifest.xml. - */ - private static final int VERSION_CODE_ATTR_ID = 0x0101021b; - private static final String MANIFEST_ELEMENT_TAG = "manifest"; - - /** - * Android resource ID of the {@code android:versionCodeMajor} attribute in AndroidManifest.xml. - */ - private static final int VERSION_CODE_MAJOR_ATTR_ID = 0x01010576; - - /** - * Returns the lowest Android platform version (API Level) supported by an APK with the - * provided {@code AndroidManifest.xml}. - * - * @param androidManifestContents contents of {@code AndroidManifest.xml} in binary Android - * resource format - * - * @throws MinSdkVersionException if an error occurred while determining the API Level - */ - public static int getMinSdkVersionFromBinaryAndroidManifest( - ByteBuffer androidManifestContents) throws MinSdkVersionException { - // IMPLEMENTATION NOTE: Minimum supported Android platform version number is declared using - // uses-sdk elements which are children of the top-level manifest element. uses-sdk element - // declares the minimum supported platform version using the android:minSdkVersion attribute - // whose default value is 1. - // For each encountered uses-sdk element, the Android runtime checks that its minSdkVersion - // is not higher than the runtime's API Level and rejects APKs if it is higher. Thus, the - // effective minSdkVersion value is the maximum over the encountered minSdkVersion values. - - try { - // If no uses-sdk elements are encountered, Android accepts the APK. We treat this - // scenario as though the minimum supported API Level is 1. - int result = 1; - - AndroidBinXmlParser parser = new AndroidBinXmlParser(androidManifestContents); - int eventType = parser.getEventType(); - while (eventType != AndroidBinXmlParser.EVENT_END_DOCUMENT) { - if ((eventType == AndroidBinXmlParser.EVENT_START_ELEMENT) - && (parser.getDepth() == 2) - && ("uses-sdk".equals(parser.getName())) - && (parser.getNamespace().isEmpty())) { - // In each uses-sdk element, minSdkVersion defaults to 1 - int minSdkVersion = 1; - for (int i = 0; i < parser.getAttributeCount(); i++) { - if (parser.getAttributeNameResourceId(i) == MIN_SDK_VERSION_ATTR_ID) { - int valueType = parser.getAttributeValueType(i); - switch (valueType) { - case AndroidBinXmlParser.VALUE_TYPE_INT: - minSdkVersion = parser.getAttributeIntValue(i); - break; - case AndroidBinXmlParser.VALUE_TYPE_STRING: - minSdkVersion = - getMinSdkVersionForCodename( - parser.getAttributeStringValue(i)); - break; - default: - throw new MinSdkVersionException( - "Unable to determine APK's minimum supported Android" - + ": unsupported value type in " - + ANDROID_MANIFEST_ZIP_ENTRY_NAME + "'s" - + " minSdkVersion" - + ". Only integer values supported."); - } - break; - } - } - result = Math.max(result, minSdkVersion); - } - eventType = parser.next(); - } - - return result; - } catch (AndroidBinXmlParser.XmlParserException e) { - throw new MinSdkVersionException( - "Unable to determine APK's minimum supported Android platform version" - + ": malformed binary resource: " + ANDROID_MANIFEST_ZIP_ENTRY_NAME, - e); - } - } - - private static class CodenamesLazyInitializer { - - /** - * List of platform codename (first letter of) to API Level mappings. The list must be - * sorted by the first letter. For codenames not in the list, the assumption is that the API - * Level is incremented by one for every increase in the codename's first letter. - */ - @SuppressWarnings({"rawtypes", "unchecked"}) - private static final Pair[] SORTED_CODENAMES_FIRST_CHAR_TO_API_LEVEL = - new Pair[] { - Pair.of('C', 2), - Pair.of('D', 3), - Pair.of('E', 4), - Pair.of('F', 7), - Pair.of('G', 8), - Pair.of('H', 10), - Pair.of('I', 13), - Pair.of('J', 15), - Pair.of('K', 18), - Pair.of('L', 20), - Pair.of('M', 22), - Pair.of('N', 23), - Pair.of('O', 25), - }; - - private static final Comparator> CODENAME_FIRST_CHAR_COMPARATOR = - new ByFirstComparator(); - - private static class ByFirstComparator implements Comparator> { - @Override - public int compare(Pair o1, Pair o2) { - char c1 = o1.getFirst(); - char c2 = o2.getFirst(); - return c1 - c2; - } - } - } - - /** - * Returns the API Level corresponding to the provided platform codename. - * - *

This method is pessimistic. It returns a value one lower than the API Level with which the - * platform is actually released (e.g., 23 for N which was released as API Level 24). This is - * because new features which first appear in an API Level are not available in the early days - * of that platform version's existence, when the platform only has a codename. Moreover, this - * method currently doesn't differentiate between initial and MR releases, meaning API Level - * returned for MR releases may be more than one lower than the API Level with which the - * platform version is actually released. - * - * @throws CodenameMinSdkVersionException if the {@code codename} is not supported - */ - static int getMinSdkVersionForCodename(String codename) throws CodenameMinSdkVersionException { - char firstChar = codename.isEmpty() ? ' ' : codename.charAt(0); - // Codenames are case-sensitive. Only codenames starting with A-Z are supported for now. - // We only look at the first letter of the codename as this is the most important letter. - if ((firstChar >= 'A') && (firstChar <= 'Z')) { - Pair[] sortedCodenamesFirstCharToApiLevel = - CodenamesLazyInitializer.SORTED_CODENAMES_FIRST_CHAR_TO_API_LEVEL; - int searchResult = - Arrays.binarySearch( - sortedCodenamesFirstCharToApiLevel, - Pair.of(firstChar, null), // second element of the pair is ignored here - CodenamesLazyInitializer.CODENAME_FIRST_CHAR_COMPARATOR); - if (searchResult >= 0) { - // Exact match -- searchResult is the index of the matching element - return sortedCodenamesFirstCharToApiLevel[searchResult].getSecond(); - } - // Not an exact match -- searchResult is negative and is -(insertion index) - 1. - // The element at insertionIndex - 1 (if present) is smaller than firstChar and the - // element at insertionIndex (if present) is greater than firstChar. - int insertionIndex = -1 - searchResult; // insertionIndex is in [0; array length] - if (insertionIndex == 0) { - // 'A' or 'B' -- never released to public - return 1; - } else { - // The element at insertionIndex - 1 is the newest older codename. - // API Level bumped by at least 1 for every change in the first letter of codename - Pair newestOlderCodenameMapping = - sortedCodenamesFirstCharToApiLevel[insertionIndex - 1]; - char newestOlderCodenameFirstChar = newestOlderCodenameMapping.getFirst(); - int newestOlderCodenameApiLevel = newestOlderCodenameMapping.getSecond(); - return newestOlderCodenameApiLevel + (firstChar - newestOlderCodenameFirstChar); - } - } - - throw new CodenameMinSdkVersionException( - "Unable to determine APK's minimum supported Android platform version" - + " : Unsupported codename in " + ANDROID_MANIFEST_ZIP_ENTRY_NAME - + "'s minSdkVersion: \"" + codename + "\"", - codename); - } - - /** - * Returns {@code true} if the APK is debuggable according to its {@code AndroidManifest.xml}. - * See the {@code android:debuggable} attribute of the {@code application} element. - * - * @param androidManifestContents contents of {@code AndroidManifest.xml} in binary Android - * resource format - * - * @throws ApkFormatException if the manifest is malformed - */ - public static boolean getDebuggableFromBinaryAndroidManifest( - ByteBuffer androidManifestContents) throws ApkFormatException { - // IMPLEMENTATION NOTE: Whether the package is debuggable is declared using the first - // "application" element which is a child of the top-level manifest element. The debuggable - // attribute of this application element is coerced to a boolean value. If there is no - // application element or if it doesn't declare the debuggable attribute, the package is - // considered not debuggable. - - try { - AndroidBinXmlParser parser = new AndroidBinXmlParser(androidManifestContents); - int eventType = parser.getEventType(); - while (eventType != AndroidBinXmlParser.EVENT_END_DOCUMENT) { - if ((eventType == AndroidBinXmlParser.EVENT_START_ELEMENT) - && (parser.getDepth() == 2) - && ("application".equals(parser.getName())) - && (parser.getNamespace().isEmpty())) { - for (int i = 0; i < parser.getAttributeCount(); i++) { - if (parser.getAttributeNameResourceId(i) == DEBUGGABLE_ATTR_ID) { - int valueType = parser.getAttributeValueType(i); - switch (valueType) { - case AndroidBinXmlParser.VALUE_TYPE_BOOLEAN: - case AndroidBinXmlParser.VALUE_TYPE_STRING: - case AndroidBinXmlParser.VALUE_TYPE_INT: - String value = parser.getAttributeStringValue(i); - return ("true".equals(value)) - || ("TRUE".equals(value)) - || ("1".equals(value)); - case AndroidBinXmlParser.VALUE_TYPE_REFERENCE: - // References to resources are not supported on purpose. The - // reason is that the resolved value depends on the resource - // configuration (e.g, MNC/MCC, locale, screen density) used - // at resolution time. As a result, the same APK may appear as - // debuggable in one situation and as non-debuggable in another - // situation. Such APKs may put users at risk. - throw new ApkFormatException( - "Unable to determine whether APK is debuggable" - + ": " + ANDROID_MANIFEST_ZIP_ENTRY_NAME + "'s" - + " android:debuggable attribute references a" - + " resource. References are not supported for" - + " security reasons. Only constant boolean," - + " string and int values are supported."); - default: - throw new ApkFormatException( - "Unable to determine whether APK is debuggable" - + ": " + ANDROID_MANIFEST_ZIP_ENTRY_NAME + "'s" - + " android:debuggable attribute uses" - + " unsupported value type. Only boolean," - + " string and int values are supported."); - } - } - } - // This application element does not declare the debuggable attribute - return false; - } - eventType = parser.next(); - } - - // No application element found - return false; - } catch (AndroidBinXmlParser.XmlParserException e) { - throw new ApkFormatException( - "Unable to determine whether APK is debuggable: malformed binary resource: " - + ANDROID_MANIFEST_ZIP_ENTRY_NAME, - e); - } - } - - /** - * Returns the package name of the APK according to its {@code AndroidManifest.xml} or - * {@code null} if package name is not declared. See the {@code package} attribute of the - * {@code manifest} element. - * - * @param androidManifestContents contents of {@code AndroidManifest.xml} in binary Android - * resource format - * - * @throws ApkFormatException if the manifest is malformed - */ - public static String getPackageNameFromBinaryAndroidManifest( - ByteBuffer androidManifestContents) throws ApkFormatException { - // IMPLEMENTATION NOTE: Package name is declared as the "package" attribute of the top-level - // manifest element. Interestingly, as opposed to most other attributes, Android Package - // Manager looks up this attribute by its name rather than by its resource ID. - - try { - AndroidBinXmlParser parser = new AndroidBinXmlParser(androidManifestContents); - int eventType = parser.getEventType(); - while (eventType != AndroidBinXmlParser.EVENT_END_DOCUMENT) { - if ((eventType == AndroidBinXmlParser.EVENT_START_ELEMENT) - && (parser.getDepth() == 1) - && ("manifest".equals(parser.getName())) - && (parser.getNamespace().isEmpty())) { - for (int i = 0; i < parser.getAttributeCount(); i++) { - if ("package".equals(parser.getAttributeName(i)) - && (parser.getNamespace().isEmpty())) { - return parser.getAttributeStringValue(i); - } - } - // No "package" attribute found - return null; - } - eventType = parser.next(); - } - - // No manifest element found - return null; - } catch (AndroidBinXmlParser.XmlParserException e) { - throw new ApkFormatException( - "Unable to determine APK package name: malformed binary resource: " - + ANDROID_MANIFEST_ZIP_ENTRY_NAME, - e); - } - } - - /** - * Returns the security sandbox version targeted by an APK with the provided - * {@code AndroidManifest.xml}. - * - *

If the security sandbox version is not specified in the manifest a default value of 1 is - * returned. - * - * @param androidManifestContents contents of {@code AndroidManifest.xml} in binary Android - * resource format - */ - public static int getTargetSandboxVersionFromBinaryAndroidManifest( - ByteBuffer androidManifestContents) { - try { - return getAttributeValueFromBinaryAndroidManifest(androidManifestContents, - MANIFEST_ELEMENT_TAG, TARGET_SANDBOX_VERSION_ATTR_ID); - } catch (ApkFormatException e) { - // An ApkFormatException indicates the target sandbox is not specified in the manifest; - // return a default value of 1. - return 1; - } - } - - /** - * Returns the SDK version targeted by an APK with the provided {@code AndroidManifest.xml}. - * - *

If the targetSdkVersion is not specified the minimumSdkVersion is returned. If neither - * value is specified then a value of 1 is returned. - * - * @param androidManifestContents contents of {@code AndroidManifest.xml} in binary Android - * resource format - */ - public static int getTargetSdkVersionFromBinaryAndroidManifest( - ByteBuffer androidManifestContents) { - // If the targetSdkVersion is not specified then the platform will use the value of the - // minSdkVersion; if neither is specified then the platform will use a value of 1. - int minSdkVersion = 1; - try { - return getAttributeValueFromBinaryAndroidManifest(androidManifestContents, - USES_SDK_ELEMENT_TAG, TARGET_SDK_VERSION_ATTR_ID); - } catch (ApkFormatException e) { - // Expected if the APK does not contain a targetSdkVersion attribute or the uses-sdk - // element is not specified at all. - } - androidManifestContents.rewind(); - try { - minSdkVersion = getMinSdkVersionFromBinaryAndroidManifest(androidManifestContents); - } catch (ApkFormatException e) { - // Similar to above, expected if the APK does not contain a minSdkVersion attribute, or - // the uses-sdk element is not specified at all. - } - return minSdkVersion; - } - - /** - * Returns the versionCode of the APK according to its {@code AndroidManifest.xml}. - * - *

If the versionCode is not specified in the {@code AndroidManifest.xml} or is not a valid - * integer an ApkFormatException is thrown. - * - * @param androidManifestContents contents of {@code AndroidManifest.xml} in binary Android - * resource format - * @throws ApkFormatException if an error occurred while determining the versionCode, or if the - * versionCode attribute value is not available. - */ - public static int getVersionCodeFromBinaryAndroidManifest(ByteBuffer androidManifestContents) - throws ApkFormatException { - return getAttributeValueFromBinaryAndroidManifest(androidManifestContents, - MANIFEST_ELEMENT_TAG, VERSION_CODE_ATTR_ID); - } - - /** - * Returns the versionCode and versionCodeMajor of the APK according to its {@code - * AndroidManifest.xml} combined together as a single long value. - * - *

The versionCodeMajor is placed in the upper 32 bits, and the versionCode is in the lower - * 32 bits. If the versionCodeMajor is not specified then the versionCode is returned. - * - * @param androidManifestContents contents of {@code AndroidManifest.xml} in binary Android - * resource format - * @throws ApkFormatException if an error occurred while determining the version, or if the - * versionCode attribute value is not available. - */ - public static long getLongVersionCodeFromBinaryAndroidManifest( - ByteBuffer androidManifestContents) throws ApkFormatException { - // If the versionCode is not found then allow the ApkFormatException to be thrown to notify - // the caller that the versionCode is not available. - int versionCode = getVersionCodeFromBinaryAndroidManifest(androidManifestContents); - long versionCodeMajor = 0; - try { - androidManifestContents.rewind(); - versionCodeMajor = getAttributeValueFromBinaryAndroidManifest(androidManifestContents, - MANIFEST_ELEMENT_TAG, VERSION_CODE_MAJOR_ATTR_ID); - } catch (ApkFormatException e) { - // This is expected if the versionCodeMajor has not been defined for the APK; in this - // case the return value is just the versionCode. - } - return (versionCodeMajor << 32) | versionCode; - } - - /** - * Returns the integer value of the requested {@code attributeId} in the specified {@code - * elementName} from the provided {@code androidManifestContents} in binary Android resource - * format. - * - * @throws ApkFormatException if an error occurred while attempting to obtain the attribute, or - * if the requested attribute is not found. - */ - private static int getAttributeValueFromBinaryAndroidManifest( - ByteBuffer androidManifestContents, String elementName, int attributeId) - throws ApkFormatException { - if (elementName == null) { - throw new NullPointerException("elementName cannot be null"); - } - try { - AndroidBinXmlParser parser = new AndroidBinXmlParser(androidManifestContents); - int eventType = parser.getEventType(); - while (eventType != AndroidBinXmlParser.EVENT_END_DOCUMENT) { - if ((eventType == AndroidBinXmlParser.EVENT_START_ELEMENT) - && (elementName.equals(parser.getName()))) { - for (int i = 0; i < parser.getAttributeCount(); i++) { - if (parser.getAttributeNameResourceId(i) == attributeId) { - int valueType = parser.getAttributeValueType(i); - switch (valueType) { - case AndroidBinXmlParser.VALUE_TYPE_INT: - case AndroidBinXmlParser.VALUE_TYPE_STRING: - return parser.getAttributeIntValue(i); - default: - throw new ApkFormatException( - "Unsupported value type, " + valueType - + ", for attribute " + String.format("0x%08X", - attributeId) + " under element " + elementName); - - } - } - } - } - eventType = parser.next(); - } - throw new ApkFormatException( - "Failed to determine APK's " + elementName + " attribute " - + String.format("0x%08X", attributeId) + " value"); - } catch (AndroidBinXmlParser.XmlParserException e) { - throw new ApkFormatException( - "Unable to determine value for attribute " + String.format("0x%08X", - attributeId) + " under element " + elementName - + "; malformed binary resource: " + ANDROID_MANIFEST_ZIP_ENTRY_NAME, e); - } - } - - public static byte[] computeSha256DigestBytes(byte[] data) { - return ApkUtilsLite.computeSha256DigestBytes(data); - } -} diff --git a/app/src/main/java/com/android/apksig/apk/ApkUtilsLite.java b/app/src/main/java/com/android/apksig/apk/ApkUtilsLite.java deleted file mode 100644 index 13f230119d..0000000000 --- a/app/src/main/java/com/android/apksig/apk/ApkUtilsLite.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.apk; - -import com.android.apksig.internal.util.Pair; -import com.android.apksig.internal.zip.ZipUtils; -import com.android.apksig.util.DataSource; -import com.android.apksig.zip.ZipFormatException; -import com.android.apksig.zip.ZipSections; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -/** - * Lightweight version of the ApkUtils for clients that only require a subset of the utility - * functionality. - */ -public class ApkUtilsLite { - private ApkUtilsLite() {} - - /** - * 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 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(); - 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); - } - - // See https://source.android.com/security/apksigning/v2.html - 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; - - /** - * Returns the APK Signing Block of the provided APK. - * - * @throws IOException if an I/O error occurs - * @throws ApkSigningBlockNotFoundException if there is no APK Signing Block in the APK - * - * @see APK Signature Scheme v2 - * - */ - public static ApkSigningBlock findApkSigningBlock(DataSource apk, ZipSections zipSections) - throws IOException, ApkSigningBlockNotFoundException { - // FORMAT (see https://source.android.com/security/apksigning/v2.html): - // 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 ApkSigningBlockNotFoundException( - "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 ApkSigningBlockNotFoundException( - "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 ApkSigningBlockNotFoundException( - "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 ApkSigningBlockNotFoundException( - "APK Signing Block size out of range: " + apkSigBlockSizeInFooter); - } - int totalSize = (int) (apkSigBlockSizeInFooter + 8); - long apkSigBlockOffset = centralDirStartOffset - totalSize; - if (apkSigBlockOffset < 0) { - throw new ApkSigningBlockNotFoundException( - "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 ApkSigningBlockNotFoundException( - "APK Signing Block sizes in header and footer do not match: " - + apkSigBlockSizeInHeader + " vs " + apkSigBlockSizeInFooter); - } - return new ApkSigningBlock(apkSigBlockOffset, apk.slice(apkSigBlockOffset, totalSize)); - } - - /** - * Information about the location of the APK Signing Block inside an APK. - */ - public static class ApkSigningBlock { - private final long mStartOffsetInApk; - private final DataSource mContents; - - /** - * Constructs a new {@code ApkSigningBlock}. - * - * @param startOffsetInApk start offset (in bytes, relative to start of file) of the APK - * Signing Block inside the APK file - * @param contents contents of the APK Signing Block - */ - public ApkSigningBlock(long startOffsetInApk, DataSource contents) { - mStartOffsetInApk = startOffsetInApk; - mContents = contents; - } - - /** - * Returns the start offset (in bytes, relative to start of file) of the APK Signing Block. - */ - public long getStartOffset() { - return mStartOffsetInApk; - } - - /** - * Returns the data source which provides the full contents of the APK Signing Block, - * including its footer. - */ - public DataSource getContents() { - return mContents; - } - } - - public static byte[] computeSha256DigestBytes(byte[] data) { - MessageDigest messageDigest; - try { - messageDigest = MessageDigest.getInstance("SHA-256"); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("SHA-256 is not found", e); - } - messageDigest.update(data); - return messageDigest.digest(); - } -} diff --git a/app/src/main/java/com/android/apksig/apk/CodenameMinSdkVersionException.java b/app/src/main/java/com/android/apksig/apk/CodenameMinSdkVersionException.java deleted file mode 100644 index e30bc359a6..0000000000 --- a/app/src/main/java/com/android/apksig/apk/CodenameMinSdkVersionException.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.apksig.apk; - -/** - * Indicates that there was an issue determining the minimum Android platform version supported by - * an APK because the version is specified as a codename, rather than as API Level number, and the - * codename is in an unexpected format. - */ -public class CodenameMinSdkVersionException extends MinSdkVersionException { - - private static final long serialVersionUID = 1L; - - /** Encountered codename. */ - private final String mCodename; - - /** - * Constructs a new {@code MinSdkVersionCodenameException} with the provided message and - * codename. - */ - public CodenameMinSdkVersionException(String message, String codename) { - super(message); - mCodename = codename; - } - - /** - * Returns the codename. - */ - public String getCodename() { - return mCodename; - } -} diff --git a/app/src/main/java/com/android/apksig/apk/MinSdkVersionException.java b/app/src/main/java/com/android/apksig/apk/MinSdkVersionException.java deleted file mode 100644 index c4aad08067..0000000000 --- a/app/src/main/java/com/android/apksig/apk/MinSdkVersionException.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.apk; - -/** - * Indicates that there was an issue determining the minimum Android platform version supported by - * an APK. - */ -public class MinSdkVersionException extends ApkFormatException { - - private static final long serialVersionUID = 1L; - - /** - * Constructs a new {@code MinSdkVersionException} with the provided message. - */ - public MinSdkVersionException(String message) { - super(message); - } - - /** - * Constructs a new {@code MinSdkVersionException} with the provided message and cause. - */ - public MinSdkVersionException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/app/src/main/java/com/android/apksig/internal/apk/AndroidBinXmlParser.java b/app/src/main/java/com/android/apksig/internal/apk/AndroidBinXmlParser.java deleted file mode 100644 index bc5a45738b..0000000000 --- a/app/src/main/java/com/android/apksig/internal/apk/AndroidBinXmlParser.java +++ /dev/null @@ -1,869 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.apk; - -import java.io.UnsupportedEncodingException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * XML pull style parser of Android binary XML resources, such as {@code AndroidManifest.xml}. - * - *

For an input document, the parser outputs an event stream (see {@code EVENT_... constants} via - * {@link #getEventType()} and {@link #next()} methods. Additional information about the current - * event can be obtained via an assortment of getters, for example, {@link #getName()} or - * {@link #getAttributeNameResourceId(int)}. - */ -public class AndroidBinXmlParser { - - /** Event: start of document. */ - public static final int EVENT_START_DOCUMENT = 1; - - /** Event: end of document. */ - public static final int EVENT_END_DOCUMENT = 2; - - /** Event: start of an element. */ - public static final int EVENT_START_ELEMENT = 3; - - /** Event: end of an document. */ - public static final int EVENT_END_ELEMENT = 4; - - /** Attribute value type is not supported by this parser. */ - public static final int VALUE_TYPE_UNSUPPORTED = 0; - - /** Attribute value is a string. Use {@link #getAttributeStringValue(int)} to obtain it. */ - public static final int VALUE_TYPE_STRING = 1; - - /** Attribute value is an integer. Use {@link #getAttributeIntValue(int)} to obtain it. */ - public static final int VALUE_TYPE_INT = 2; - - /** - * Attribute value is a resource reference. Use {@link #getAttributeIntValue(int)} to obtain it. - */ - public static final int VALUE_TYPE_REFERENCE = 3; - - /** Attribute value is a boolean. Use {@link #getAttributeBooleanValue(int)} to obtain it. */ - public static final int VALUE_TYPE_BOOLEAN = 4; - - private static final long NO_NAMESPACE = 0xffffffffL; - - private final ByteBuffer mXml; - - private StringPool mStringPool; - private ResourceMap mResourceMap; - private int mDepth; - private int mCurrentEvent = EVENT_START_DOCUMENT; - - private String mCurrentElementName; - private String mCurrentElementNamespace; - private int mCurrentElementAttributeCount; - private List mCurrentElementAttributes; - private ByteBuffer mCurrentElementAttributesContents; - private int mCurrentElementAttrSizeBytes; - - /** - * Constructs a new parser for the provided document. - */ - public AndroidBinXmlParser(ByteBuffer xml) throws XmlParserException { - xml.order(ByteOrder.LITTLE_ENDIAN); - - Chunk resXmlChunk = null; - while (xml.hasRemaining()) { - Chunk chunk = Chunk.get(xml); - if (chunk == null) { - break; - } - if (chunk.getType() == Chunk.TYPE_RES_XML) { - resXmlChunk = chunk; - break; - } - } - - if (resXmlChunk == null) { - throw new XmlParserException("No XML chunk in file"); - } - mXml = resXmlChunk.getContents(); - } - - /** - * Returns the depth of the current element. Outside of the root of the document the depth is - * {@code 0}. The depth is incremented by {@code 1} before each {@code start element} event and - * is decremented by {@code 1} after each {@code end element} event. - */ - public int getDepth() { - return mDepth; - } - - /** - * Returns the type of the current event. See {@code EVENT_...} constants. - */ - public int getEventType() { - return mCurrentEvent; - } - - /** - * Returns the local name of the current element or {@code null} if the current event does not - * pertain to an element. - */ - public String getName() { - if ((mCurrentEvent != EVENT_START_ELEMENT) && (mCurrentEvent != EVENT_END_ELEMENT)) { - return null; - } - return mCurrentElementName; - } - - /** - * Returns the namespace of the current element or {@code null} if the current event does not - * pertain to an element. Returns an empty string if the element is not associated with a - * namespace. - */ - public String getNamespace() { - if ((mCurrentEvent != EVENT_START_ELEMENT) && (mCurrentEvent != EVENT_END_ELEMENT)) { - return null; - } - return mCurrentElementNamespace; - } - - /** - * Returns the number of attributes of the element associated with the current event or - * {@code -1} if no element is associated with the current event. - */ - public int getAttributeCount() { - if (mCurrentEvent != EVENT_START_ELEMENT) { - return -1; - } - - return mCurrentElementAttributeCount; - } - - /** - * Returns the resource ID corresponding to the name of the specified attribute of the current - * element or {@code 0} if the name is not associated with a resource ID. - * - * @throws IndexOutOfBoundsException if the index is out of range or the current event is not a - * {@code start element} event - * @throws XmlParserException if a parsing error is occurred - */ - public int getAttributeNameResourceId(int index) throws XmlParserException { - return getAttribute(index).getNameResourceId(); - } - - /** - * Returns the name of the specified attribute of the current element. - * - * @throws IndexOutOfBoundsException if the index is out of range or the current event is not a - * {@code start element} event - * @throws XmlParserException if a parsing error is occurred - */ - public String getAttributeName(int index) throws XmlParserException { - return getAttribute(index).getName(); - } - - /** - * Returns the name of the specified attribute of the current element or an empty string if - * the attribute is not associated with a namespace. - * - * @throws IndexOutOfBoundsException if the index is out of range or the current event is not a - * {@code start element} event - * @throws XmlParserException if a parsing error is occurred - */ - public String getAttributeNamespace(int index) throws XmlParserException { - return getAttribute(index).getNamespace(); - } - - /** - * Returns the value type of the specified attribute of the current element. See - * {@code VALUE_TYPE_...} constants. - * - * @throws IndexOutOfBoundsException if the index is out of range or the current event is not a - * {@code start element} event - * @throws XmlParserException if a parsing error is occurred - */ - public int getAttributeValueType(int index) throws XmlParserException { - int type = getAttribute(index).getValueType(); - switch (type) { - case Attribute.TYPE_STRING: - return VALUE_TYPE_STRING; - case Attribute.TYPE_INT_DEC: - case Attribute.TYPE_INT_HEX: - return VALUE_TYPE_INT; - case Attribute.TYPE_REFERENCE: - return VALUE_TYPE_REFERENCE; - case Attribute.TYPE_INT_BOOLEAN: - return VALUE_TYPE_BOOLEAN; - default: - return VALUE_TYPE_UNSUPPORTED; - } - } - - /** - * Returns the integer value of the specified attribute of the current element. See - * {@code VALUE_TYPE_...} constants. - * - * @throws IndexOutOfBoundsException if the index is out of range or the current event is not a - * {@code start element} event. - * @throws XmlParserException if a parsing error is occurred - */ - public int getAttributeIntValue(int index) throws XmlParserException { - return getAttribute(index).getIntValue(); - } - - /** - * Returns the boolean value of the specified attribute of the current element. See - * {@code VALUE_TYPE_...} constants. - * - * @throws IndexOutOfBoundsException if the index is out of range or the current event is not a - * {@code start element} event. - * @throws XmlParserException if a parsing error is occurred - */ - public boolean getAttributeBooleanValue(int index) throws XmlParserException { - return getAttribute(index).getBooleanValue(); - } - - /** - * Returns the string value of the specified attribute of the current element. See - * {@code VALUE_TYPE_...} constants. - * - * @throws IndexOutOfBoundsException if the index is out of range or the current event is not a - * {@code start element} event. - * @throws XmlParserException if a parsing error is occurred - */ - public String getAttributeStringValue(int index) throws XmlParserException { - return getAttribute(index).getStringValue(); - } - - private Attribute getAttribute(int index) { - if (mCurrentEvent != EVENT_START_ELEMENT) { - throw new IndexOutOfBoundsException("Current event not a START_ELEMENT"); - } - if (index < 0) { - throw new IndexOutOfBoundsException("index must be >= 0"); - } - if (index >= mCurrentElementAttributeCount) { - throw new IndexOutOfBoundsException( - "index must be <= attr count (" + mCurrentElementAttributeCount + ")"); - } - parseCurrentElementAttributesIfNotParsed(); - return mCurrentElementAttributes.get(index); - } - - /** - * Advances to the next parsing event and returns its type. See {@code EVENT_...} constants. - */ - public int next() throws XmlParserException { - // Decrement depth if the previous event was "end element". - if (mCurrentEvent == EVENT_END_ELEMENT) { - mDepth--; - } - - // Read events from document, ignoring events that we don't report to caller. Stop at the - // earliest event which we report to caller. - while (mXml.hasRemaining()) { - Chunk chunk = Chunk.get(mXml); - if (chunk == null) { - break; - } - switch (chunk.getType()) { - case Chunk.TYPE_STRING_POOL: - if (mStringPool != null) { - throw new XmlParserException("Multiple string pools not supported"); - } - mStringPool = new StringPool(chunk); - break; - - case Chunk.RES_XML_TYPE_START_ELEMENT: - { - if (mStringPool == null) { - throw new XmlParserException( - "Named element encountered before string pool"); - } - ByteBuffer contents = chunk.getContents(); - if (contents.remaining() < 20) { - throw new XmlParserException( - "Start element chunk too short. Need at least 20 bytes. Available: " - + contents.remaining() + " bytes"); - } - long nsId = getUnsignedInt32(contents); - long nameId = getUnsignedInt32(contents); - int attrStartOffset = getUnsignedInt16(contents); - int attrSizeBytes = getUnsignedInt16(contents); - int attrCount = getUnsignedInt16(contents); - long attrEndOffset = attrStartOffset + ((long) attrCount) * attrSizeBytes; - contents.position(0); - if (attrStartOffset > contents.remaining()) { - throw new XmlParserException( - "Attributes start offset out of bounds: " + attrStartOffset - + ", max: " + contents.remaining()); - } - if (attrEndOffset > contents.remaining()) { - throw new XmlParserException( - "Attributes end offset out of bounds: " + attrEndOffset - + ", max: " + contents.remaining()); - } - - mCurrentElementName = mStringPool.getString(nameId); - mCurrentElementNamespace = - (nsId == NO_NAMESPACE) ? "" : mStringPool.getString(nsId); - mCurrentElementAttributeCount = attrCount; - mCurrentElementAttributes = null; - mCurrentElementAttrSizeBytes = attrSizeBytes; - mCurrentElementAttributesContents = - sliceFromTo(contents, attrStartOffset, attrEndOffset); - - mDepth++; - mCurrentEvent = EVENT_START_ELEMENT; - return mCurrentEvent; - } - - case Chunk.RES_XML_TYPE_END_ELEMENT: - { - if (mStringPool == null) { - throw new XmlParserException( - "Named element encountered before string pool"); - } - ByteBuffer contents = chunk.getContents(); - if (contents.remaining() < 8) { - throw new XmlParserException( - "End element chunk too short. Need at least 8 bytes. Available: " - + contents.remaining() + " bytes"); - } - long nsId = getUnsignedInt32(contents); - long nameId = getUnsignedInt32(contents); - mCurrentElementName = mStringPool.getString(nameId); - mCurrentElementNamespace = - (nsId == NO_NAMESPACE) ? "" : mStringPool.getString(nsId); - mCurrentEvent = EVENT_END_ELEMENT; - mCurrentElementAttributes = null; - mCurrentElementAttributesContents = null; - return mCurrentEvent; - } - case Chunk.RES_XML_TYPE_RESOURCE_MAP: - if (mResourceMap != null) { - throw new XmlParserException("Multiple resource maps not supported"); - } - mResourceMap = new ResourceMap(chunk); - break; - default: - // Unknown chunk type -- ignore - break; - } - } - - mCurrentEvent = EVENT_END_DOCUMENT; - return mCurrentEvent; - } - - private void parseCurrentElementAttributesIfNotParsed() { - if (mCurrentElementAttributes != null) { - return; - } - mCurrentElementAttributes = new ArrayList<>(mCurrentElementAttributeCount); - for (int i = 0; i < mCurrentElementAttributeCount; i++) { - int startPosition = i * mCurrentElementAttrSizeBytes; - ByteBuffer attr = - sliceFromTo( - mCurrentElementAttributesContents, - startPosition, - startPosition + mCurrentElementAttrSizeBytes); - long nsId = getUnsignedInt32(attr); - long nameId = getUnsignedInt32(attr); - attr.position(attr.position() + 7); // skip ignored fields - int valueType = getUnsignedInt8(attr); - long valueData = getUnsignedInt32(attr); - mCurrentElementAttributes.add( - new Attribute( - nsId, - nameId, - valueType, - (int) valueData, - mStringPool, - mResourceMap)); - } - } - - private static class Attribute { - private static final int TYPE_REFERENCE = 1; - private static final int TYPE_STRING = 3; - private static final int TYPE_INT_DEC = 0x10; - private static final int TYPE_INT_HEX = 0x11; - private static final int TYPE_INT_BOOLEAN = 0x12; - - private final long mNsId; - private final long mNameId; - private final int mValueType; - private final int mValueData; - private final StringPool mStringPool; - private final ResourceMap mResourceMap; - - private Attribute( - long nsId, - long nameId, - int valueType, - int valueData, - StringPool stringPool, - ResourceMap resourceMap) { - mNsId = nsId; - mNameId = nameId; - mValueType = valueType; - mValueData = valueData; - mStringPool = stringPool; - mResourceMap = resourceMap; - } - - public int getNameResourceId() { - return (mResourceMap != null) ? mResourceMap.getResourceId(mNameId) : 0; - } - - public String getName() throws XmlParserException { - return mStringPool.getString(mNameId); - } - - public String getNamespace() throws XmlParserException { - return (mNsId != NO_NAMESPACE) ? mStringPool.getString(mNsId) : ""; - } - - public int getValueType() { - return mValueType; - } - - public int getIntValue() throws XmlParserException { - switch (mValueType) { - case TYPE_REFERENCE: - case TYPE_INT_DEC: - case TYPE_INT_HEX: - case TYPE_INT_BOOLEAN: - return mValueData; - default: - throw new XmlParserException("Cannot coerce to int: value type " + mValueType); - } - } - - public boolean getBooleanValue() throws XmlParserException { - switch (mValueType) { - case TYPE_INT_BOOLEAN: - return mValueData != 0; - default: - throw new XmlParserException( - "Cannot coerce to boolean: value type " + mValueType); - } - } - - public String getStringValue() throws XmlParserException { - switch (mValueType) { - case TYPE_STRING: - return mStringPool.getString(mValueData & 0xffffffffL); - case TYPE_INT_DEC: - return Integer.toString(mValueData); - case TYPE_INT_HEX: - return "0x" + Integer.toHexString(mValueData); - case TYPE_INT_BOOLEAN: - return Boolean.toString(mValueData != 0); - case TYPE_REFERENCE: - return "@" + Integer.toHexString(mValueData); - default: - throw new XmlParserException( - "Cannot coerce to string: value type " + mValueType); - } - } - } - - /** - * Chunk of a document. Each chunk is tagged with a type and consists of a header followed by - * contents. - */ - private static class Chunk { - public static final int TYPE_STRING_POOL = 1; - public static final int TYPE_RES_XML = 3; - public static final int RES_XML_TYPE_START_ELEMENT = 0x0102; - public static final int RES_XML_TYPE_END_ELEMENT = 0x0103; - public static final int RES_XML_TYPE_RESOURCE_MAP = 0x0180; - - static final int HEADER_MIN_SIZE_BYTES = 8; - - private final int mType; - private final ByteBuffer mHeader; - private final ByteBuffer mContents; - - public Chunk(int type, ByteBuffer header, ByteBuffer contents) { - mType = type; - mHeader = header; - mContents = contents; - } - - public ByteBuffer getContents() { - ByteBuffer result = mContents.slice(); - result.order(mContents.order()); - return result; - } - - public ByteBuffer getHeader() { - ByteBuffer result = mHeader.slice(); - result.order(mHeader.order()); - return result; - } - - public int getType() { - return mType; - } - - /** - * Consumes the chunk located at the current position of the input and returns the chunk - * or {@code null} if there is no chunk left in the input. - * - * @throws XmlParserException if the chunk is malformed - */ - public static Chunk get(ByteBuffer input) throws XmlParserException { - if (input.remaining() < HEADER_MIN_SIZE_BYTES) { - // Android ignores the last chunk if its header is too big to fit into the file - input.position(input.limit()); - return null; - } - - int originalPosition = input.position(); - int type = getUnsignedInt16(input); - int headerSize = getUnsignedInt16(input); - long chunkSize = getUnsignedInt32(input); - long chunkRemaining = chunkSize - 8; - if (chunkRemaining > input.remaining()) { - // Android ignores the last chunk if it's too big to fit into the file - input.position(input.limit()); - return null; - } - if (headerSize < HEADER_MIN_SIZE_BYTES) { - throw new XmlParserException( - "Malformed chunk: header too short: " + headerSize + " bytes"); - } else if (headerSize > chunkSize) { - throw new XmlParserException( - "Malformed chunk: header too long: " + headerSize + " bytes. Chunk size: " - + chunkSize + " bytes"); - } - int contentStartPosition = originalPosition + headerSize; - long chunkEndPosition = originalPosition + chunkSize; - Chunk chunk = - new Chunk( - type, - sliceFromTo(input, originalPosition, contentStartPosition), - sliceFromTo(input, contentStartPosition, chunkEndPosition)); - input.position((int) chunkEndPosition); - return chunk; - } - } - - /** - * String pool of a document. Strings are referenced by their {@code 0}-based index in the pool. - */ - private static class StringPool { - private static final int FLAG_UTF8 = 1 << 8; - - private final ByteBuffer mChunkContents; - private final ByteBuffer mStringsSection; - private final int mStringCount; - private final boolean mUtf8Encoded; - private final Map mCachedStrings = new HashMap<>(); - - /** - * Constructs a new string pool from the provided chunk. - * - * @throws XmlParserException if a parsing error occurred - */ - public StringPool(Chunk chunk) throws XmlParserException { - ByteBuffer header = chunk.getHeader(); - int headerSizeBytes = header.remaining(); - header.position(Chunk.HEADER_MIN_SIZE_BYTES); - if (header.remaining() < 20) { - throw new XmlParserException( - "XML chunk's header too short. Required at least 20 bytes. Available: " - + header.remaining() + " bytes"); - } - long stringCount = getUnsignedInt32(header); - if (stringCount > Integer.MAX_VALUE) { - throw new XmlParserException("Too many strings: " + stringCount); - } - mStringCount = (int) stringCount; - long styleCount = getUnsignedInt32(header); - if (styleCount > Integer.MAX_VALUE) { - throw new XmlParserException("Too many styles: " + styleCount); - } - long flags = getUnsignedInt32(header); - long stringsStartOffset = getUnsignedInt32(header); - long stylesStartOffset = getUnsignedInt32(header); - - ByteBuffer contents = chunk.getContents(); - if (mStringCount > 0) { - int stringsSectionStartOffsetInContents = - (int) (stringsStartOffset - headerSizeBytes); - int stringsSectionEndOffsetInContents; - if (styleCount > 0) { - // Styles section follows the strings section - if (stylesStartOffset < stringsStartOffset) { - throw new XmlParserException( - "Styles offset (" + stylesStartOffset + ") < strings offset (" - + stringsStartOffset + ")"); - } - stringsSectionEndOffsetInContents = (int) (stylesStartOffset - headerSizeBytes); - } else { - stringsSectionEndOffsetInContents = contents.remaining(); - } - mStringsSection = - sliceFromTo( - contents, - stringsSectionStartOffsetInContents, - stringsSectionEndOffsetInContents); - } else { - mStringsSection = ByteBuffer.allocate(0); - } - - mUtf8Encoded = (flags & FLAG_UTF8) != 0; - mChunkContents = contents; - } - - /** - * Returns the string located at the specified {@code 0}-based index in this pool. - * - * @throws XmlParserException if the string does not exist or cannot be decoded - */ - public String getString(long index) throws XmlParserException { - if (index < 0) { - throw new XmlParserException("Unsuported string index: " + index); - } else if (index >= mStringCount) { - throw new XmlParserException( - "Unsuported string index: " + index + ", max: " + (mStringCount - 1)); - } - - int idx = (int) index; - String result = mCachedStrings.get(idx); - if (result != null) { - return result; - } - - long offsetInStringsSection = getUnsignedInt32(mChunkContents, idx * 4); - if (offsetInStringsSection >= mStringsSection.capacity()) { - throw new XmlParserException( - "Offset of string idx " + idx + " out of bounds: " + offsetInStringsSection - + ", max: " + (mStringsSection.capacity() - 1)); - } - mStringsSection.position((int) offsetInStringsSection); - result = - (mUtf8Encoded) - ? getLengthPrefixedUtf8EncodedString(mStringsSection) - : getLengthPrefixedUtf16EncodedString(mStringsSection); - mCachedStrings.put(idx, result); - return result; - } - - private static String getLengthPrefixedUtf16EncodedString(ByteBuffer encoded) - throws XmlParserException { - // If the length (in uint16s) is 0x7fff or lower, it is stored as a single uint16. - // Otherwise, it is stored as a big-endian uint32 with highest bit set. Thus, the range - // of supported values is 0 to 0x7fffffff inclusive. - int lengthChars = getUnsignedInt16(encoded); - if ((lengthChars & 0x8000) != 0) { - lengthChars = ((lengthChars & 0x7fff) << 16) | getUnsignedInt16(encoded); - } - if (lengthChars > Integer.MAX_VALUE / 2) { - throw new XmlParserException("String too long: " + lengthChars + " uint16s"); - } - int lengthBytes = lengthChars * 2; - - byte[] arr; - int arrOffset; - if (encoded.hasArray()) { - arr = encoded.array(); - arrOffset = encoded.arrayOffset() + encoded.position(); - encoded.position(encoded.position() + lengthBytes); - } else { - arr = new byte[lengthBytes]; - arrOffset = 0; - encoded.get(arr); - } - // Reproduce the behavior of Android runtime which requires that the UTF-16 encoded - // array of bytes is NULL terminated. - if ((arr[arrOffset + lengthBytes] != 0) - || (arr[arrOffset + lengthBytes + 1] != 0)) { - throw new XmlParserException("UTF-16 encoded form of string not NULL terminated"); - } - try { - return new String(arr, arrOffset, lengthBytes, "UTF-16LE"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException("UTF-16LE character encoding not supported", e); - } - } - - private static String getLengthPrefixedUtf8EncodedString(ByteBuffer encoded) - throws XmlParserException { - // If the length (in bytes) is 0x7f or lower, it is stored as a single uint8. Otherwise, - // it is stored as a big-endian uint16 with highest bit set. Thus, the range of - // supported values is 0 to 0x7fff inclusive. - - // Skip UTF-16 encoded length (in uint16s) - int lengthBytes = getUnsignedInt8(encoded); - if ((lengthBytes & 0x80) != 0) { - lengthBytes = ((lengthBytes & 0x7f) << 8) | getUnsignedInt8(encoded); - } - - // Read UTF-8 encoded length (in bytes) - lengthBytes = getUnsignedInt8(encoded); - if ((lengthBytes & 0x80) != 0) { - lengthBytes = ((lengthBytes & 0x7f) << 8) | getUnsignedInt8(encoded); - } - - byte[] arr; - int arrOffset; - if (encoded.hasArray()) { - arr = encoded.array(); - arrOffset = encoded.arrayOffset() + encoded.position(); - encoded.position(encoded.position() + lengthBytes); - } else { - arr = new byte[lengthBytes]; - arrOffset = 0; - encoded.get(arr); - } - // Reproduce the behavior of Android runtime which requires that the UTF-8 encoded array - // of bytes is NULL terminated. - if (arr[arrOffset + lengthBytes] != 0) { - throw new XmlParserException("UTF-8 encoded form of string not NULL terminated"); - } - try { - return new String(arr, arrOffset, lengthBytes, "UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException("UTF-8 character encoding not supported", e); - } - } - } - - /** - * Resource map of a document. Resource IDs are referenced by their {@code 0}-based index in the - * map. - */ - private static class ResourceMap { - private final ByteBuffer mChunkContents; - private final int mEntryCount; - - /** - * Constructs a new resource map from the provided chunk. - * - * @throws XmlParserException if a parsing error occurred - */ - public ResourceMap(Chunk chunk) throws XmlParserException { - mChunkContents = chunk.getContents().slice(); - mChunkContents.order(chunk.getContents().order()); - // Each entry of the map is four bytes long, containing the int32 resource ID. - mEntryCount = mChunkContents.remaining() / 4; - } - - /** - * Returns the resource ID located at the specified {@code 0}-based index in this pool or - * {@code 0} if the index is out of range. - */ - public int getResourceId(long index) { - if ((index < 0) || (index >= mEntryCount)) { - return 0; - } - int idx = (int) index; - // Each entry of the map is four bytes long, containing the int32 resource ID. - return mChunkContents.getInt(idx * 4); - } - } - - /** - * 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, long start, long 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); - } - return sliceFromTo(source, (int) start, (int) end); - } - - /** - * 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); - } - } - - private static int getUnsignedInt8(ByteBuffer buffer) { - return buffer.get() & 0xff; - } - - private static int getUnsignedInt16(ByteBuffer buffer) { - return buffer.getShort() & 0xffff; - } - - private static long getUnsignedInt32(ByteBuffer buffer) { - return buffer.getInt() & 0xffffffffL; - } - - private static long getUnsignedInt32(ByteBuffer buffer, int position) { - return buffer.getInt(position) & 0xffffffffL; - } - - /** - * Indicates that an error occurred while parsing a document. - */ - public static class XmlParserException extends Exception { - private static final long serialVersionUID = 1L; - - public XmlParserException(String message) { - super(message); - } - - public XmlParserException(String message, Throwable cause) { - super(message, cause); - } - } -} diff --git a/app/src/main/java/com/android/apksig/internal/apk/ApkSigResult.java b/app/src/main/java/com/android/apksig/internal/apk/ApkSigResult.java deleted file mode 100644 index 6151351b2b..0000000000 --- a/app/src/main/java/com/android/apksig/internal/apk/ApkSigResult.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.apk; - -import com.android.apksig.ApkVerificationIssue; - -import java.util.ArrayList; -import java.util.List; - -/** - * Base implementation of an APK signature verification result. - */ -public class ApkSigResult { - public final int signatureSchemeVersion; - - /** Whether the APK's Signature Scheme signature verifies. */ - public boolean verified; - - public final List mSigners = new ArrayList<>(); - private final List mWarnings = new ArrayList<>(); - private final List mErrors = new ArrayList<>(); - - public ApkSigResult(int signatureSchemeVersion) { - this.signatureSchemeVersion = signatureSchemeVersion; - } - - /** - * Returns {@code true} if this result encountered errors during verification. - */ - public boolean containsErrors() { - if (!mErrors.isEmpty()) { - return true; - } - if (!mSigners.isEmpty()) { - for (ApkSignerInfo signer : mSigners) { - if (signer.containsErrors()) { - return true; - } - } - } - return false; - } - - /** - * Returns {@code true} if this result encountered warnings during verification. - */ - public boolean containsWarnings() { - if (!mWarnings.isEmpty()) { - return true; - } - if (!mSigners.isEmpty()) { - for (ApkSignerInfo signer : mSigners) { - if (signer.containsWarnings()) { - return true; - } - } - } - return false; - } - - /** - * Adds a new {@link ApkVerificationIssue} as an error to this result using the provided {@code - * issueId} and {@code params}. - */ - public void addError(int issueId, Object... parameters) { - mErrors.add(new ApkVerificationIssue(issueId, parameters)); - } - - /** - * Adds a new {@link ApkVerificationIssue} as a warning to this result using the provided {@code - * issueId} and {@code params}. - */ - public void addWarning(int issueId, Object... parameters) { - mWarnings.add(new ApkVerificationIssue(issueId, parameters)); - } - - /** - * Returns the errors encountered during verification. - */ - public List getErrors() { - return mErrors; - } - - /** - * Returns the warnings encountered during verification. - */ - public List getWarnings() { - return mWarnings; - } -} diff --git a/app/src/main/java/com/android/apksig/internal/apk/ApkSignerInfo.java b/app/src/main/java/com/android/apksig/internal/apk/ApkSignerInfo.java deleted file mode 100644 index e0ea365d20..0000000000 --- a/app/src/main/java/com/android/apksig/internal/apk/ApkSignerInfo.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.apk; - -import com.android.apksig.ApkVerificationIssue; - -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.List; - -/** - * Base implementation of an APK signer. - */ -public class ApkSignerInfo { - public int index; - public List certs = new ArrayList<>(); - public List certificateLineage = new ArrayList<>(); - - private final List mWarnings = new ArrayList<>(); - private final List mErrors = new ArrayList<>(); - - /** - * Adds a new {@link ApkVerificationIssue} as an error to this signer using the provided {@code - * issueId} and {@code params}. - */ - public void addError(int issueId, Object... params) { - mErrors.add(new ApkVerificationIssue(issueId, params)); - } - - /** - * Adds a new {@link ApkVerificationIssue} as a warning to this signer using the provided {@code - * issueId} and {@code params}. - */ - public void addWarning(int issueId, Object... params) { - mWarnings.add(new ApkVerificationIssue(issueId, params)); - } - - /** - * Returns {@code true} if any errors were encountered during verification for this signer. - */ - public boolean containsErrors() { - return !mErrors.isEmpty(); - } - - /** - * Returns {@code true} if any warnings were encountered during verification for this signer. - */ - public boolean containsWarnings() { - return !mWarnings.isEmpty(); - } - - /** - * Returns the errors encountered during verification for this signer. - */ - public List getErrors() { - return mErrors; - } - - /** - * Returns the warnings encountered during verification for this signer. - */ - public List getWarnings() { - return mWarnings; - } -} diff --git a/app/src/main/java/com/android/apksig/internal/apk/ApkSigningBlockUtils.java b/app/src/main/java/com/android/apksig/internal/apk/ApkSigningBlockUtils.java deleted file mode 100644 index 5b3484947f..0000000000 --- a/app/src/main/java/com/android/apksig/internal/apk/ApkSigningBlockUtils.java +++ /dev/null @@ -1,1442 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.apk; - -import static com.android.apksig.Constants.OID_RSA_ENCRYPTION; -import static com.android.apksig.internal.apk.ContentDigestAlgorithm.CHUNKED_SHA256; -import static com.android.apksig.internal.apk.ContentDigestAlgorithm.CHUNKED_SHA512; -import static com.android.apksig.internal.apk.ContentDigestAlgorithm.VERITY_CHUNKED_SHA256; - -import com.android.apksig.ApkVerifier; -import com.android.apksig.SigningCertificateLineage; -import com.android.apksig.apk.ApkFormatException; -import com.android.apksig.apk.ApkUtils; -import com.android.apksig.internal.asn1.Asn1BerParser; -import com.android.apksig.internal.asn1.Asn1DecodingException; -import com.android.apksig.internal.asn1.Asn1DerEncoder; -import com.android.apksig.internal.asn1.Asn1EncodingException; -import com.android.apksig.internal.asn1.Asn1OpaqueObject; -import com.android.apksig.internal.pkcs7.AlgorithmIdentifier; -import com.android.apksig.internal.pkcs7.ContentInfo; -import com.android.apksig.internal.pkcs7.EncapsulatedContentInfo; -import com.android.apksig.internal.pkcs7.IssuerAndSerialNumber; -import com.android.apksig.internal.pkcs7.Pkcs7Constants; -import com.android.apksig.internal.pkcs7.SignedData; -import com.android.apksig.internal.pkcs7.SignerIdentifier; -import com.android.apksig.internal.pkcs7.SignerInfo; -import com.android.apksig.internal.util.ByteBufferDataSource; -import com.android.apksig.internal.util.ChainedDataSource; -import com.android.apksig.internal.util.GuaranteedEncodedFormX509Certificate; -import com.android.apksig.internal.util.Pair; -import com.android.apksig.internal.util.VerityTreeBuilder; -import com.android.apksig.internal.util.X509CertificateUtils; -import com.android.apksig.internal.x509.RSAPublicKey; -import com.android.apksig.internal.x509.SubjectPublicKeyInfo; -import com.android.apksig.internal.zip.ZipUtils; -import com.android.apksig.util.DataSink; -import com.android.apksig.util.DataSinks; -import com.android.apksig.util.DataSource; -import com.android.apksig.util.DataSources; -import com.android.apksig.util.RunnablesExecutor; - -import java.io.IOException; -import java.math.BigInteger; -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.CertificateException; -import java.security.cert.X509Certificate; -import java.security.spec.AlgorithmParameterSpec; -import java.security.spec.InvalidKeySpecException; -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; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Supplier; - -import javax.security.auth.x500.X500Principal; - -public class ApkSigningBlockUtils { - - private static final long CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES = 1024 * 1024; - public static final int ANDROID_COMMON_PAGE_ALIGNMENT_BYTES = 4096; - 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, - }; - public static final int VERITY_PADDING_BLOCK_ID = 0x42726577; - - private static final ContentDigestAlgorithm[] V4_CONTENT_DIGEST_ALGORITHMS = - {CHUNKED_SHA512, VERITY_CHUNKED_SHA256, CHUNKED_SHA256}; - - public static final int VERSION_SOURCE_STAMP = 0; - public static final int VERSION_JAR_SIGNATURE_SCHEME = 1; - public static final int VERSION_APK_SIGNATURE_SCHEME_V2 = 2; - public static final int VERSION_APK_SIGNATURE_SCHEME_V3 = 3; - public static final int VERSION_APK_SIGNATURE_SCHEME_V4 = 4; - - /** - * 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. - */ - public static int compareSignatureAlgorithm(SignatureAlgorithm alg1, SignatureAlgorithm alg2) { - return ApkSigningBlockUtilsLite.compareSignatureAlgorithm(alg1, alg2); - } - - /** - * 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 - * are taken from {@code SignerInfos} of the provided {@code result}. - * - *

This method adds one or more errors to the {@code result} if a verification error is - * expected to be encountered on Android. No errors are added to the {@code result} if the APK's - * integrity is expected to verify on Android for each algorithm in - * {@code contentDigestAlgorithms}. - * - *

The reason this method is currently not parameterized by a - * {@code [minSdkVersion, maxSdkVersion]} range is that up until now content digest algorithms - * exhibit the same behavior on all Android platform versions. - */ - public static void verifyIntegrity( - RunnablesExecutor executor, - DataSource beforeApkSigningBlock, - DataSource centralDir, - ByteBuffer eocd, - Set 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()); - int eocdSavedPos = eocd.position(); - modifiedEocd.order(ByteOrder.LITTLE_ENDIAN); - modifiedEocd.put(eocd); - modifiedEocd.flip(); - - // restore eocd to position prior to modification in case it is to be used elsewhere - eocd.position(eocdSavedPos); - ZipUtils.setZipEocdCentralDirectoryOffset(modifiedEocd, beforeApkSigningBlock.size()); - Map actualContentDigests; - try { - actualContentDigests = - computeContentDigests( - executor, - contentDigestAlgorithms, - beforeApkSigningBlock, - centralDir, - new ByteBufferDataSource(modifiedEocd)); - // Special checks for the verity algorithm requirements. - if (actualContentDigests.containsKey(VERITY_CHUNKED_SHA256)) { - if ((beforeApkSigningBlock.size() % ANDROID_COMMON_PAGE_ALIGNMENT_BYTES != 0)) { - throw new RuntimeException( - "APK Signing Block is not aligned on 4k boundary: " + - beforeApkSigningBlock.size()); - } - - long centralDirOffset = ZipUtils.getZipEocdCentralDirectoryOffset(eocd); - long signingBlockSize = centralDirOffset - beforeApkSigningBlock.size(); - if (signingBlockSize % ANDROID_COMMON_PAGE_ALIGNMENT_BYTES != 0) { - throw new RuntimeException( - "APK Signing Block size is not multiple of page size: " + - signingBlockSize); - } - } - } 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(); - // if the current digest algorithm is not in the list provided by the caller then - // ignore it; the signer may contain digests not recognized by the specified SDK - // range. - if (!contentDigestAlgorithms.contains(contentDigestAlgorithm)) { - continue; - } - byte[] expectedDigest = expected.getValue(); - byte[] actualDigest = actualContentDigests.get(contentDigestAlgorithm); - if (!Arrays.equals(expectedDigest, actualDigest)) { - if (result.signatureSchemeVersion == VERSION_APK_SIGNATURE_SCHEME_V2) { - signerInfo.addError( - ApkVerifier.Issue.V2_SIG_APK_DIGEST_DID_NOT_VERIFY, - contentDigestAlgorithm, - toHex(expectedDigest), - toHex(actualDigest)); - } else if (result.signatureSchemeVersion == VERSION_APK_SIGNATURE_SCHEME_V3) { - signerInfo.addError( - ApkVerifier.Issue.V3_SIG_APK_DIGEST_DID_NOT_VERIFY, - contentDigestAlgorithm, - toHex(expectedDigest), - toHex(actualDigest)); - } - continue; - } - signerInfo.verifiedContentDigests.put(contentDigestAlgorithm, actualDigest); - } - } - } - - public static ByteBuffer findApkSignatureSchemeBlock( - ByteBuffer apkSigningBlock, - int blockId, - Result result) throws SignatureNotFoundException { - try { - return ApkSigningBlockUtilsLite.findApkSignatureSchemeBlock(apkSigningBlock, blockId); - } catch (com.android.apksig.internal.apk.SignatureNotFoundException e) { - throw new SignatureNotFoundException(e.getMessage()); - } - } - - public static void checkByteOrderLittleEndian(ByteBuffer buffer) { - ApkSigningBlockUtilsLite.checkByteOrderLittleEndian(buffer); - } - - public static ByteBuffer getLengthPrefixedSlice(ByteBuffer source) throws ApkFormatException { - return ApkSigningBlockUtilsLite.getLengthPrefixedSlice(source); - } - - public static byte[] readLengthPrefixedByteArray(ByteBuffer buf) throws ApkFormatException { - return ApkSigningBlockUtilsLite.readLengthPrefixedByteArray(buf); - } - - public static String toHex(byte[] value) { - return ApkSigningBlockUtilsLite.toHex(value); - } - - public static Map computeContentDigests( - RunnablesExecutor executor, - Set digestAlgorithms, - DataSource beforeCentralDir, - DataSource centralDir, - DataSource eocd) throws IOException, NoSuchAlgorithmException, DigestException { - Map contentDigests = new HashMap<>(); - Set oneMbChunkBasedAlgorithm = new HashSet<>(); - for (ContentDigestAlgorithm digestAlgorithm : digestAlgorithms) { - if (digestAlgorithm == ContentDigestAlgorithm.CHUNKED_SHA256 - || digestAlgorithm == ContentDigestAlgorithm.CHUNKED_SHA512) { - oneMbChunkBasedAlgorithm.add(digestAlgorithm); - } - } - computeOneMbChunkContentDigests( - executor, - oneMbChunkBasedAlgorithm, - new DataSource[] { beforeCentralDir, centralDir, eocd }, - contentDigests); - - if (digestAlgorithms.contains(VERITY_CHUNKED_SHA256)) { - computeApkVerityDigest(beforeCentralDir, centralDir, eocd, contentDigests); - } - return contentDigests; - } - - static void computeOneMbChunkContentDigests( - Set digestAlgorithms, - DataSource[] contents, - Map outputContentDigests) - 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); - } - - DataSink mdSink = DataSinks.asDataSink(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++; - } - } - - 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); - outputContentDigests.put(digestAlgorithm, digest); - } - } - - static void computeOneMbChunkContentDigests( - RunnablesExecutor executor, - Set digestAlgorithms, - DataSource[] contents, - Map outputContentDigests) - throws NoSuchAlgorithmException, DigestException { - 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; - - List chunkDigestsList = new ArrayList<>(digestAlgorithms.size()); - for (ContentDigestAlgorithm algorithms : digestAlgorithms) { - chunkDigestsList.add(new ChunkDigests(algorithms, chunkCount)); - } - - ChunkSupplier chunkSupplier = new ChunkSupplier(contents); - executor.execute(() -> new ChunkDigester(chunkSupplier, chunkDigestsList)); - - // Compute and write out final digest for each algorithm. - for (ChunkDigests chunkDigests : chunkDigestsList) { - MessageDigest messageDigest = chunkDigests.createMessageDigest(); - outputContentDigests.put( - chunkDigests.algorithm, - messageDigest.digest(chunkDigests.concatOfDigestsOfChunks)); - } - } - - private static class ChunkDigests { - private final ContentDigestAlgorithm algorithm; - private final int digestOutputSize; - private final byte[] concatOfDigestsOfChunks; - - private ChunkDigests(ContentDigestAlgorithm algorithm, int chunkCount) { - this.algorithm = algorithm; - digestOutputSize = this.algorithm.getChunkDigestOutputSizeBytes(); - concatOfDigestsOfChunks = new byte[1 + 4 + chunkCount * digestOutputSize]; - - // Fill the initial values of the concatenated digests of chunks, which is - // {0x5a, 4-bytes-of-little-endian-chunk-count, digests*...}. - concatOfDigestsOfChunks[0] = 0x5a; - setUnsignedInt32LittleEndian(chunkCount, concatOfDigestsOfChunks, 1); - } - - private MessageDigest createMessageDigest() throws NoSuchAlgorithmException { - return MessageDigest.getInstance(algorithm.getJcaMessageDigestAlgorithm()); - } - - private int getOffset(int chunkIndex) { - return 1 + 4 + chunkIndex * digestOutputSize; - } - } - - /** - * A per-thread digest worker. - */ - private static class ChunkDigester implements Runnable { - private final ChunkSupplier dataSupplier; - private final List chunkDigests; - private final List messageDigests; - private final DataSink mdSink; - - private ChunkDigester(ChunkSupplier dataSupplier, List chunkDigests) { - this.dataSupplier = dataSupplier; - this.chunkDigests = chunkDigests; - messageDigests = new ArrayList<>(chunkDigests.size()); - for (ChunkDigests chunkDigest : chunkDigests) { - try { - messageDigests.add(chunkDigest.createMessageDigest()); - } catch (NoSuchAlgorithmException ex) { - throw new RuntimeException(ex); - } - } - mdSink = DataSinks.asDataSink(messageDigests.toArray(new MessageDigest[0])); - } - - @Override - public void run() { - byte[] chunkContentPrefix = new byte[5]; - chunkContentPrefix[0] = (byte) 0xa5; - - try { - for (ChunkSupplier.Chunk chunk = dataSupplier.get(); - chunk != null; - chunk = dataSupplier.get()) { - int size = chunk.size; - if (size > CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES) { - throw new RuntimeException("Chunk size greater than expected: " + size); - } - - // First update with the chunk prefix. - setUnsignedInt32LittleEndian(size, chunkContentPrefix, 1); - mdSink.consume(chunkContentPrefix, 0, chunkContentPrefix.length); - - // Then update with the chunk data. - mdSink.consume(chunk.data); - - // Now finalize chunk for all algorithms. - for (int i = 0; i < chunkDigests.size(); i++) { - ChunkDigests chunkDigest = chunkDigests.get(i); - int actualDigestSize = messageDigests.get(i).digest( - chunkDigest.concatOfDigestsOfChunks, - chunkDigest.getOffset(chunk.chunkIndex), - chunkDigest.digestOutputSize); - if (actualDigestSize != chunkDigest.digestOutputSize) { - throw new RuntimeException( - "Unexpected output size of " + chunkDigest.algorithm - + " digest: " + actualDigestSize); - } - } - } - } catch (IOException | DigestException e) { - throw new RuntimeException(e); - } - } - } - - /** - * Thread-safe 1MB DataSource chunk supplier. When bounds are met in a - * supplied {@link DataSource}, the data from the next {@link DataSource} - * are NOT concatenated. Only the next call to get() will fetch from the - * next {@link DataSource} in the input {@link DataSource} array. - */ - private static class ChunkSupplier implements Supplier { - private final DataSource[] dataSources; - private final int[] chunkCounts; - private final int totalChunkCount; - private final AtomicInteger nextIndex; - - private ChunkSupplier(DataSource[] dataSources) { - this.dataSources = dataSources; - chunkCounts = new int[dataSources.length]; - int totalChunkCount = 0; - for (int i = 0; i < dataSources.length; i++) { - long chunkCount = getChunkCount(dataSources[i].size(), - CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES); - if (chunkCount > Integer.MAX_VALUE) { - throw new RuntimeException( - String.format( - "Number of chunks in dataSource[%d] is greater than max int.", - i)); - } - chunkCounts[i] = (int)chunkCount; - totalChunkCount = (int) (totalChunkCount + chunkCount); - } - this.totalChunkCount = totalChunkCount; - nextIndex = new AtomicInteger(0); - } - - /** - * We map an integer index to the termination-adjusted dataSources 1MB chunks. - * Note that {@link Chunk}s could be less than 1MB, namely the last 1MB-aligned - * blocks in each input {@link DataSource} (unless the DataSource itself is - * 1MB-aligned). - */ - @Override - public ChunkSupplier.Chunk get() { - int index = nextIndex.getAndIncrement(); - if (index < 0 || index >= totalChunkCount) { - return null; - } - - int dataSourceIndex = 0; - long dataSourceChunkOffset = index; - for (; dataSourceIndex < dataSources.length; dataSourceIndex++) { - if (dataSourceChunkOffset < chunkCounts[dataSourceIndex]) { - break; - } - dataSourceChunkOffset -= chunkCounts[dataSourceIndex]; - } - - long remainingSize = Math.min( - dataSources[dataSourceIndex].size() - - dataSourceChunkOffset * CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES, - CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES); - - final int size = (int)remainingSize; - final ByteBuffer buffer = ByteBuffer.allocate(size); - try { - dataSources[dataSourceIndex].copyTo( - dataSourceChunkOffset * CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES, size, - buffer); - } catch (IOException e) { - throw new IllegalStateException("Failed to read chunk", e); - } - buffer.rewind(); - - return new Chunk(index, buffer, size); - } - - static class Chunk { - private final int chunkIndex; - private final ByteBuffer data; - private final int size; - - private Chunk(int chunkIndex, ByteBuffer data, int size) { - this.chunkIndex = chunkIndex; - this.data = data; - this.size = size; - } - } - } - - @SuppressWarnings("ByteBufferBackingArray") - private static void computeApkVerityDigest(DataSource beforeCentralDir, DataSource centralDir, - DataSource eocd, Map outputContentDigests) - throws IOException, NoSuchAlgorithmException { - ByteBuffer encoded = createVerityDigestBuffer(true); - // Use 0s as salt for now. This also needs to be consistent in the fsverify header for - // kernel to use. - try (VerityTreeBuilder builder = new VerityTreeBuilder(new byte[8])) { - byte[] rootHash = builder.generateVerityTreeRootHash(beforeCentralDir, centralDir, - eocd); - encoded.put(rootHash); - encoded.putLong(beforeCentralDir.size() + centralDir.size() + eocd.size()); - outputContentDigests.put(VERITY_CHUNKED_SHA256, encoded.array()); - } - } - - private static ByteBuffer createVerityDigestBuffer(boolean includeSourceDataSize) { - // FORMAT: - // OFFSET DATA TYPE DESCRIPTION - // * @+0 bytes uint8[32] Merkle tree root hash of SHA-256 - // * @+32 bytes int64 (optional) Length of source data - int backBufferSize = - VERITY_CHUNKED_SHA256.getChunkDigestOutputSizeBytes(); - if (includeSourceDataSize) { - backBufferSize += Long.SIZE / Byte.SIZE; - } - ByteBuffer encoded = ByteBuffer.allocate(backBufferSize); - encoded.order(ByteOrder.LITTLE_ENDIAN); - return encoded; - } - - public static class VerityTreeAndDigest { - public final ContentDigestAlgorithm contentDigestAlgorithm; - public final byte[] rootHash; - public final byte[] tree; - - VerityTreeAndDigest(ContentDigestAlgorithm contentDigestAlgorithm, byte[] rootHash, - byte[] tree) { - this.contentDigestAlgorithm = contentDigestAlgorithm; - this.rootHash = rootHash; - this.tree = tree; - } - } - - @SuppressWarnings("ByteBufferBackingArray") - public static VerityTreeAndDigest computeChunkVerityTreeAndDigest(DataSource dataSource) - throws IOException, NoSuchAlgorithmException { - ByteBuffer encoded = createVerityDigestBuffer(false); - // Use 0s as salt for now. This also needs to be consistent in the fsverify header for - // kernel to use. - try (VerityTreeBuilder builder = new VerityTreeBuilder(null)) { - ByteBuffer tree = builder.generateVerityTree(dataSource); - byte[] rootHash = builder.getRootHashFromTree(tree); - encoded.put(rootHash); - return new VerityTreeAndDigest(VERITY_CHUNKED_SHA256, encoded.array(), tree.array()); - } - } - - private static long getChunkCount(long inputSize, long 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); - } - - public static byte[] encodePublicKey(PublicKey publicKey) - throws InvalidKeyException, NoSuchAlgorithmException { - byte[] encodedPublicKey = null; - if ("X.509".equals(publicKey.getFormat())) { - encodedPublicKey = publicKey.getEncoded(); - // if the key is an RSA key check for a negative modulus - String keyAlgorithm = publicKey.getAlgorithm(); - if ("RSA".equals(keyAlgorithm) || OID_RSA_ENCRYPTION.equals(keyAlgorithm)) { - try { - // Parse the encoded public key into the separate elements of the - // SubjectPublicKeyInfo to obtain the SubjectPublicKey. - ByteBuffer encodedPublicKeyBuffer = ByteBuffer.wrap(encodedPublicKey); - SubjectPublicKeyInfo subjectPublicKeyInfo = Asn1BerParser.parse( - encodedPublicKeyBuffer, SubjectPublicKeyInfo.class); - // The SubjectPublicKey is encoded as a bit string within the - // SubjectPublicKeyInfo. The first byte of the encoding is the number of padding - // bits; store this and decode the rest of the bit string into the RSA modulus - // and exponent. - ByteBuffer subjectPublicKeyBuffer = subjectPublicKeyInfo.subjectPublicKey; - byte padding = subjectPublicKeyBuffer.get(); - RSAPublicKey rsaPublicKey = Asn1BerParser.parse(subjectPublicKeyBuffer, - RSAPublicKey.class); - // if the modulus is negative then attempt to reencode it with a leading 0 sign - // byte. - if (rsaPublicKey.modulus.compareTo(BigInteger.ZERO) < 0) { - // A negative modulus indicates the leading bit in the integer is 1. Per - // ASN.1 encoding rules to encode a positive integer with the leading bit - // set to 1 a byte containing all zeros should precede the integer encoding. - byte[] encodedModulus = rsaPublicKey.modulus.toByteArray(); - byte[] reencodedModulus = new byte[encodedModulus.length + 1]; - reencodedModulus[0] = 0; - System.arraycopy(encodedModulus, 0, reencodedModulus, 1, - encodedModulus.length); - rsaPublicKey.modulus = new BigInteger(reencodedModulus); - // Once the modulus has been corrected reencode the RSAPublicKey, then - // restore the padding value in the bit string and reencode the entire - // SubjectPublicKeyInfo to be returned to the caller. - byte[] reencodedRSAPublicKey = Asn1DerEncoder.encode(rsaPublicKey); - byte[] reencodedSubjectPublicKey = - new byte[reencodedRSAPublicKey.length + 1]; - reencodedSubjectPublicKey[0] = padding; - System.arraycopy(reencodedRSAPublicKey, 0, reencodedSubjectPublicKey, 1, - reencodedRSAPublicKey.length); - subjectPublicKeyInfo.subjectPublicKey = ByteBuffer.wrap( - reencodedSubjectPublicKey); - encodedPublicKey = Asn1DerEncoder.encode(subjectPublicKeyInfo); - } - } catch (Asn1DecodingException | Asn1EncodingException e) { - System.out.println("Caught a exception encoding the public key: " + e); - e.printStackTrace(); - encodedPublicKey = null; - } - } - } - 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; - } - - public static List encodeCertificates(List certificates) - throws CertificateEncodingException { - List result = new ArrayList<>(certificates.size()); - for (X509Certificate certificate : certificates) { - result.add(certificate.getEncoded()); - } - return result; - } - - public static byte[] encodeAsLengthPrefixedElement(byte[] bytes) { - byte[][] adapterBytes = new byte[1][]; - adapterBytes[0] = bytes; - return encodeAsSequenceOfLengthPrefixedElements(adapterBytes); - } - - public static byte[] encodeAsSequenceOfLengthPrefixedElements(List sequence) { - return encodeAsSequenceOfLengthPrefixedElements( - sequence.toArray(new byte[sequence.size()][])); - } - - public 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(); - } - - public static byte[] encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes( - List> sequence) { - return ApkSigningBlockUtilsLite - .encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes(sequence); - } - - /** - * Returns the APK Signature Scheme block contained in the provided APK file for the given ID - * and the additional information relevant for verifying the block against the file. - * - * @param blockId the ID value in the APK Signing Block's sequence of ID-value pairs - * identifying the appropriate block to find, e.g. the APK Signature Scheme v2 - * block ID. - * - * @throws SignatureNotFoundException if the APK is not signed using given APK Signature Scheme - * @throws IOException if an I/O error occurs while reading the APK - */ - public static SignatureInfo findSignature( - DataSource apk, ApkUtils.ZipSections zipSections, int blockId, Result result) - throws IOException, SignatureNotFoundException { - try { - return ApkSigningBlockUtilsLite.findSignature(apk, zipSections, blockId); - } catch (com.android.apksig.internal.apk.SignatureNotFoundException e) { - throw new SignatureNotFoundException(e.getMessage()); - } - } - - /** - * Generates a new DataSource representing the APK contents before the Central Directory with - * padding, if padding is requested. If the existing data entries before the Central Directory - * are already aligned, or no padding is requested, the original DataSource is used. This - * padding is used to allow for verity-based APK verification. - * - * @return {@code Pair} containing the potentially new {@code DataSource} and the amount of - * padding used. - */ - public static Pair generateApkSigningBlockPadding( - DataSource beforeCentralDir, - boolean apkSigningBlockPaddingSupported) { - - // Ensure APK Signing Block starts from page boundary. - int padSizeBeforeSigningBlock = 0; - if (apkSigningBlockPaddingSupported && - (beforeCentralDir.size() % ANDROID_COMMON_PAGE_ALIGNMENT_BYTES != 0)) { - padSizeBeforeSigningBlock = (int) ( - ANDROID_COMMON_PAGE_ALIGNMENT_BYTES - - beforeCentralDir.size() % ANDROID_COMMON_PAGE_ALIGNMENT_BYTES); - beforeCentralDir = new ChainedDataSource( - beforeCentralDir, - DataSources.asDataSource( - ByteBuffer.allocate(padSizeBeforeSigningBlock))); - } - return Pair.of(beforeCentralDir, padSizeBeforeSigningBlock); - } - - public static DataSource copyWithModifiedCDOffset( - DataSource beforeCentralDir, DataSource eocd) throws IOException { - - // 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); - return DataSources.asDataSource(eocdBuf); - } - - public static byte[] generateApkSigningBlock( - List> apkSignatureSchemeBlockPairs) { - // FORMAT: - // uint64: size (excluding this field) - // repeated ID-value pairs: - // uint64: size (excluding this field) - // uint32: ID - // (size - 4) bytes: value - // (extra verity ID-value for padding to make block size a multiple of 4096 bytes) - // uint64: size (same as the one above) - // uint128: magic - - int blocksSize = 0; - for (Pair schemeBlockPair : apkSignatureSchemeBlockPairs) { - blocksSize += 8 + 4 + schemeBlockPair.getFirst().length; // size + id + value - } - - int resultSize = - 8 // size - + blocksSize - + 8 // size - + 16 // magic - ; - ByteBuffer paddingPair = null; - if (resultSize % ANDROID_COMMON_PAGE_ALIGNMENT_BYTES != 0) { - int padding = ANDROID_COMMON_PAGE_ALIGNMENT_BYTES - - (resultSize % ANDROID_COMMON_PAGE_ALIGNMENT_BYTES); - if (padding < 12) { // minimum size of an ID-value pair - padding += ANDROID_COMMON_PAGE_ALIGNMENT_BYTES; - } - paddingPair = ByteBuffer.allocate(padding).order(ByteOrder.LITTLE_ENDIAN); - paddingPair.putLong(padding - 8); - paddingPair.putInt(VERITY_PADDING_BLOCK_ID); - paddingPair.rewind(); - resultSize += padding; - } - - ByteBuffer result = ByteBuffer.allocate(resultSize); - result.order(ByteOrder.LITTLE_ENDIAN); - long blockSizeFieldValue = resultSize - 8L; - result.putLong(blockSizeFieldValue); - - for (Pair schemeBlockPair : apkSignatureSchemeBlockPairs) { - byte[] apkSignatureSchemeBlock = schemeBlockPair.getFirst(); - int apkSignatureSchemeId = schemeBlockPair.getSecond(); - long pairSizeFieldValue = 4L + apkSignatureSchemeBlock.length; - result.putLong(pairSizeFieldValue); - result.putInt(apkSignatureSchemeId); - result.put(apkSignatureSchemeBlock); - } - - if (paddingPair != null) { - result.put(paddingPair); - } - - result.putLong(blockSizeFieldValue); - result.put(APK_SIGNING_BLOCK_MAGIC); - - return result.array(); - } - - /** - * Returns the individual APK signature blocks within the provided {@code apkSigningBlock} in a - * {@code List} of {@code Pair} instances where the first element in the {@code Pair} is the - * contents / value of the signature block and the second element is the ID of the block. - * - * @throws IOException if an error is encountered reading the provided {@code apkSigningBlock} - */ - public static List> getApkSignatureBlocks( - DataSource apkSigningBlock) throws IOException { - // FORMAT: - // uint64: size (excluding this field) - // repeated ID-value pairs: - // uint64: size (excluding this field) - // uint32: ID - // (size - 4) bytes: value - // (extra verity ID-value for padding to make block size a multiple of 4096 bytes) - // uint64: size (same as the one above) - // uint128: magic - long apkSigningBlockSize = apkSigningBlock.size(); - if (apkSigningBlock.size() > Integer.MAX_VALUE || apkSigningBlockSize < 32) { - throw new IllegalArgumentException( - "APK signing block size out of range: " + apkSigningBlockSize); - } - // Remove the header and footer from the signing block to iterate over only the repeated - // ID-value pairs. - ByteBuffer apkSigningBlockBuffer = apkSigningBlock.getByteBuffer(8, - (int) apkSigningBlock.size() - 32); - apkSigningBlockBuffer.order(ByteOrder.LITTLE_ENDIAN); - List> signatureBlocks = new ArrayList<>(); - while (apkSigningBlockBuffer.hasRemaining()) { - long blockLength = apkSigningBlockBuffer.getLong(); - if (blockLength > Integer.MAX_VALUE || blockLength < 4) { - throw new IllegalArgumentException( - "Block index " + (signatureBlocks.size() + 1) + " size out of range: " - + blockLength); - } - int blockId = apkSigningBlockBuffer.getInt(); - // Since the block ID has already been read from the signature block read the next - // blockLength - 4 bytes as the value. - byte[] blockValue = new byte[(int) blockLength - 4]; - apkSigningBlockBuffer.get(blockValue); - signatureBlocks.add(Pair.of(blockValue, blockId)); - } - return signatureBlocks; - } - - /** - * Returns the individual APK signers within the provided {@code signatureBlock} in a {@code - * List} of {@code Pair} instances where the first element is a {@code List} of {@link - * X509Certificate}s and the second element is a byte array of the individual signer's block. - * - *

This method supports any signature block that adheres to the following format up to the - * signing certificate(s): - *

-     * * length-prefixed sequence of length-prefixed signers
-     *   * length-prefixed signed data
-     *     * length-prefixed sequence of length-prefixed digests:
-     *       * uint32: signature algorithm ID
-     *       * length-prefixed bytes: digest of contents
-     *     * length-prefixed sequence of certificates:
-     *       * length-prefixed bytes: X.509 certificate (ASN.1 DER encoded).
-     * 
- * - *

Note, this is a convenience method to obtain any signers from an existing signature block; - * the signature of each signer will not be verified. - * - * @throws ApkFormatException if an error is encountered while parsing the provided {@code - * signatureBlock} - * @throws CertificateException if the signing certificate(s) within an individual signer block - * cannot be parsed - */ - public static List, byte[]>> getApkSignatureBlockSigners( - byte[] signatureBlock) throws ApkFormatException, CertificateException { - ByteBuffer signatureBlockBuffer = ByteBuffer.wrap(signatureBlock); - signatureBlockBuffer.order(ByteOrder.LITTLE_ENDIAN); - ByteBuffer signersBuffer = getLengthPrefixedSlice(signatureBlockBuffer); - List, byte[]>> signers = new ArrayList<>(); - while (signersBuffer.hasRemaining()) { - // Parse the next signer block, save all of its bytes for the resulting List, and - // rewind the buffer to allow the signing certificate(s) to be parsed. - ByteBuffer signer = getLengthPrefixedSlice(signersBuffer); - byte[] signerBytes = new byte[signer.remaining()]; - signer.get(signerBytes); - signer.rewind(); - - ByteBuffer signedData = getLengthPrefixedSlice(signer); - // The first length prefixed slice is the sequence of digests which are not required - // when obtaining the signing certificate(s). - getLengthPrefixedSlice(signedData); - ByteBuffer certificatesBuffer = getLengthPrefixedSlice(signedData); - List certificates = new ArrayList<>(); - while (certificatesBuffer.hasRemaining()) { - int certLength = certificatesBuffer.getInt(); - byte[] certBytes = new byte[certLength]; - if (certLength > certificatesBuffer.remaining()) { - throw new IllegalArgumentException( - "Cert index " + (certificates.size() + 1) + " under signer index " - + (signers.size() + 1) + " size out of range: " + certLength); - } - certificatesBuffer.get(certBytes); - GuaranteedEncodedFormX509Certificate signerCert = - new GuaranteedEncodedFormX509Certificate( - X509CertificateUtils.generateCertificate(certBytes), certBytes); - certificates.add(signerCert); - } - signers.add(Pair.of(certificates, signerBytes)); - } - return signers; - } - - /** - * Computes the digests of the given APK components according to the algorithms specified in the - * given SignerConfigs. - * - * @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 SignatureException if an error occurs when computing digests of generating - * signatures - */ - public static Pair, Map> - computeContentDigests( - RunnablesExecutor executor, - DataSource beforeCentralDir, - DataSource centralDir, - DataSource eocd, - List signerConfigs) - throws IOException, NoSuchAlgorithmException, 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 contentDigestAlgorithms = new HashSet<>(1); - for (SignerConfig signerConfig : signerConfigs) { - for (SignatureAlgorithm signatureAlgorithm : signerConfig.signatureAlgorithms) { - contentDigestAlgorithms.add(signatureAlgorithm.getContentDigestAlgorithm()); - } - } - - // Compute digests of APK contents. - Map contentDigests; // digest algorithm ID -> digest - try { - contentDigests = - computeContentDigests( - executor, - contentDigestAlgorithms, - beforeCentralDir, - centralDir, - eocd); - } 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 Pair.of(signerConfigs, contentDigests); - } - - /** - * Returns the subset of signatures which are expected to be verified by at least one Android - * platform version in the {@code [minSdkVersion, maxSdkVersion]} range. The returned result is - * guaranteed to contain at least one signature. - * - *

Each Android platform version typically verifies exactly one signature from the provided - * {@code signatures} set. This method returns the set of these signatures collected over all - * requested platform versions. As a result, the result may contain more than one signature. - * - * @throws NoSupportedSignaturesException if no supported signatures were - * found for an Android platform version in the range. - */ - public static List getSignaturesToVerify( - List signatures, int minSdkVersion, int maxSdkVersion) - throws NoSupportedSignaturesException { - return getSignaturesToVerify(signatures, minSdkVersion, maxSdkVersion, false); - } - - /** - * Returns the subset of signatures which are expected to be verified by at least one Android - * platform version in the {@code [minSdkVersion, maxSdkVersion]} range. The returned result is - * guaranteed to contain at least one signature. - * - *

{@code onlyRequireJcaSupport} can be set to true for cases that only require verifying a - * signature within the signing block using the standard JCA. - * - *

Each Android platform version typically verifies exactly one signature from the provided - * {@code signatures} set. This method returns the set of these signatures collected over all - * requested platform versions. As a result, the result may contain more than one signature. - * - * @throws NoSupportedSignaturesException if no supported signatures were - * found for an Android platform version in the range. - */ - public static List getSignaturesToVerify( - List signatures, int minSdkVersion, int maxSdkVersion, - boolean onlyRequireJcaSupport) throws NoSupportedSignaturesException { - try { - return ApkSigningBlockUtilsLite.getSignaturesToVerify(signatures, minSdkVersion, - maxSdkVersion, onlyRequireJcaSupport); - } catch (NoApkSupportedSignaturesException e) { - throw new NoSupportedSignaturesException(e.getMessage()); - } - } - - public static class NoSupportedSignaturesException extends NoApkSupportedSignaturesException { - public NoSupportedSignaturesException(String message) { - super(message); - } - } - - 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); - } - } - - /** - * uses the SignatureAlgorithms in the provided signerConfig to sign the provided data - * - * @return list of signature algorithm IDs and their corresponding signatures over the data. - */ - public static List> generateSignaturesOverData( - SignerConfig signerConfig, byte[] data) - throws InvalidKeyException, NoSuchAlgorithmException, SignatureException { - List> signatures = - new ArrayList<>(signerConfig.signatureAlgorithms.size()); - PublicKey publicKey = signerConfig.certificates.get(0).getPublicKey(); - for (SignatureAlgorithm signatureAlgorithm : signerConfig.signatureAlgorithms) { - Pair 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(data); - 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(data); - if (!signature.verify(signatureBytes)) { - throw new SignatureException("Failed to verify generated " - + jcaSignatureAlgorithm - + " signature using public key from certificate"); - } - } 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); - } - - signatures.add(Pair.of(signatureAlgorithm.getId(), signatureBytes)); - } - return signatures; - } - - /** - * Wrap the signature according to CMS PKCS #7 RFC 5652. - * The high-level simplified structure is as follows: - * // ContentInfo - * // digestAlgorithm - * // SignedData - * // bag of certificates - * // SignerInfo - * // signing cert issuer and serial number (for locating the cert in the above bag) - * // digestAlgorithm - * // signatureAlgorithm - * // signature - * - * @throws Asn1EncodingException if the ASN.1 structure could not be encoded - */ - public static byte[] generatePkcs7DerEncodedMessage( - byte[] signatureBytes, ByteBuffer data, List signerCerts, - AlgorithmIdentifier digestAlgorithmId, AlgorithmIdentifier signatureAlgorithmId) - throws Asn1EncodingException, CertificateEncodingException { - SignerInfo signerInfo = new SignerInfo(); - signerInfo.version = 1; - X509Certificate signingCert = signerCerts.get(0); - X500Principal signerCertIssuer = signingCert.getIssuerX500Principal(); - signerInfo.sid = - new SignerIdentifier( - new IssuerAndSerialNumber( - new Asn1OpaqueObject(signerCertIssuer.getEncoded()), - signingCert.getSerialNumber())); - - signerInfo.digestAlgorithm = digestAlgorithmId; - signerInfo.signatureAlgorithm = signatureAlgorithmId; - signerInfo.signature = ByteBuffer.wrap(signatureBytes); - - SignedData signedData = new SignedData(); - signedData.certificates = new ArrayList<>(signerCerts.size()); - for (X509Certificate cert : signerCerts) { - signedData.certificates.add(new Asn1OpaqueObject(cert.getEncoded())); - } - signedData.version = 1; - signedData.digestAlgorithms = Collections.singletonList(digestAlgorithmId); - signedData.encapContentInfo = new EncapsulatedContentInfo(Pkcs7Constants.OID_DATA); - // If data is not null, data will be embedded as is in the result -- an attached pcsk7 - signedData.encapContentInfo.content = data; - signedData.signerInfos = Collections.singletonList(signerInfo); - ContentInfo contentInfo = new ContentInfo(); - contentInfo.contentType = Pkcs7Constants.OID_SIGNED_DATA; - contentInfo.content = new Asn1OpaqueObject(Asn1DerEncoder.encode(signedData)); - return Asn1DerEncoder.encode(contentInfo); - } - - /** - * Picks the correct v2/v3 digest for v4 signature verification. - * - * Keep in sync with pickBestDigestForV4 in framework's ApkSigningBlockUtils. - */ - public static byte[] pickBestDigestForV4(Map contentDigests) { - for (ContentDigestAlgorithm algo : V4_CONTENT_DIGEST_ALGORITHMS) { - if (contentDigests.containsKey(algo)) { - return contentDigests.get(algo); - } - } - return null; - } - - /** - * 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 certificates; - - /** - * List of signature algorithms with which to sign. - */ - public List signatureAlgorithms; - - public int minSdkVersion; - public int maxSdkVersion; - public SigningCertificateLineage mSigningCertificateLineage; - } - - public static class Result extends ApkSigResult { - public SigningCertificateLineage signingCertificateLineage = null; - public final List signers = new ArrayList<>(); - private final List mWarnings = new ArrayList<>(); - private final List mErrors = new ArrayList<>(); - - public Result(int signatureSchemeVersion) { - super(signatureSchemeVersion); - } - - public boolean containsErrors() { - if (!mErrors.isEmpty()) { - return true; - } - if (!signers.isEmpty()) { - for (Result.SignerInfo signer : signers) { - if (signer.containsErrors()) { - return true; - } - } - } - return false; - } - - public boolean containsWarnings() { - if (!mWarnings.isEmpty()) { - return true; - } - if (!signers.isEmpty()) { - for (Result.SignerInfo signer : signers) { - if (signer.containsWarnings()) { - return true; - } - } - } - return false; - } - - public void addError(ApkVerifier.Issue msg, Object... parameters) { - mErrors.add(new ApkVerifier.IssueWithParams(msg, parameters)); - } - - public void addWarning(ApkVerifier.Issue msg, Object... parameters) { - mWarnings.add(new ApkVerifier.IssueWithParams(msg, parameters)); - } - - @Override - public List getErrors() { - return mErrors; - } - - @Override - public List getWarnings() { - return mWarnings; - } - - public static class SignerInfo extends ApkSignerInfo { - public List contentDigests = new ArrayList<>(); - public Map verifiedContentDigests = new HashMap<>(); - public List signatures = new ArrayList<>(); - public Map verifiedSignatures = new HashMap<>(); - public List additionalAttributes = new ArrayList<>(); - public byte[] signedData; - public int minSdkVersion; - public int maxSdkVersion; - public SigningCertificateLineage signingCertificateLineage; - - private final List mWarnings = new ArrayList<>(); - private final List mErrors = new ArrayList<>(); - - public void addError(ApkVerifier.Issue msg, Object... parameters) { - mErrors.add(new ApkVerifier.IssueWithParams(msg, parameters)); - } - - public void addWarning(ApkVerifier.Issue msg, Object... parameters) { - mWarnings.add(new ApkVerifier.IssueWithParams(msg, parameters)); - } - - public boolean containsErrors() { - return !mErrors.isEmpty(); - } - - public boolean containsWarnings() { - return !mWarnings.isEmpty(); - } - - public List getErrors() { - return mErrors; - } - - public List 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(); - } - } - } - } - - public static class SupportedSignature extends ApkSupportedSignature { - public SupportedSignature(SignatureAlgorithm algorithm, byte[] signature) { - super(algorithm, signature); - } - } - - public static class SigningSchemeBlockAndDigests { - public final Pair signingSchemeBlock; - public final Map digestInfo; - - public SigningSchemeBlockAndDigests( - Pair signingSchemeBlock, - Map digestInfo) { - this.signingSchemeBlock = signingSchemeBlock; - this.digestInfo = digestInfo; - } - } -} diff --git a/app/src/main/java/com/android/apksig/internal/apk/ApkSigningBlockUtilsLite.java b/app/src/main/java/com/android/apksig/internal/apk/ApkSigningBlockUtilsLite.java deleted file mode 100644 index 40ae94798a..0000000000 --- a/app/src/main/java/com/android/apksig/internal/apk/ApkSigningBlockUtilsLite.java +++ /dev/null @@ -1,393 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.apk; - -import com.android.apksig.apk.ApkFormatException; -import com.android.apksig.apk.ApkSigningBlockNotFoundException; -import com.android.apksig.apk.ApkUtilsLite; -import com.android.apksig.internal.util.Pair; -import com.android.apksig.util.DataSource; -import com.android.apksig.zip.ZipSections; - -import java.io.IOException; -import java.nio.BufferUnderflowException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Lightweight version of the ApkSigningBlockUtils for clients that only require a subset of the - * utility functionality. - */ -public class ApkSigningBlockUtilsLite { - private ApkSigningBlockUtilsLite() {} - - private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray(); - /** - * Returns the APK Signature Scheme block contained in the provided APK file for the given ID - * and the additional information relevant for verifying the block against the file. - * - * @param blockId the ID value in the APK Signing Block's sequence of ID-value pairs - * identifying the appropriate block to find, e.g. the APK Signature Scheme v2 - * block ID. - * - * @throws SignatureNotFoundException if the APK is not signed using given APK Signature Scheme - * @throws IOException if an I/O error occurs while reading the APK - */ - public static SignatureInfo findSignature( - DataSource apk, ZipSections zipSections, int blockId) - throws IOException, SignatureNotFoundException { - // Find the APK Signing Block. - DataSource apkSigningBlock; - long apkSigningBlockOffset; - try { - ApkUtilsLite.ApkSigningBlock apkSigningBlockInfo = - ApkUtilsLite.findApkSigningBlock(apk, zipSections); - apkSigningBlockOffset = apkSigningBlockInfo.getStartOffset(); - apkSigningBlock = apkSigningBlockInfo.getContents(); - } catch (ApkSigningBlockNotFoundException e) { - throw new SignatureNotFoundException(e.getMessage(), e); - } - ByteBuffer apkSigningBlockBuf = - apkSigningBlock.getByteBuffer(0, (int) apkSigningBlock.size()); - apkSigningBlockBuf.order(ByteOrder.LITTLE_ENDIAN); - - // Find the APK Signature Scheme Block inside the APK Signing Block. - ByteBuffer apkSignatureSchemeBlock = - findApkSignatureSchemeBlock(apkSigningBlockBuf, blockId); - return new SignatureInfo( - apkSignatureSchemeBlock, - apkSigningBlockOffset, - zipSections.getZipCentralDirectoryOffset(), - zipSections.getZipEndOfCentralDirectoryOffset(), - zipSections.getZipEndOfCentralDirectory()); - } - - public static ByteBuffer findApkSignatureSchemeBlock( - ByteBuffer apkSigningBlock, - int blockId) throws SignatureNotFoundException { - checkByteOrderLittleEndian(apkSigningBlock); - // FORMAT: - // OFFSET DATA TYPE DESCRIPTION - // * @+0 bytes uint64: size in bytes (excluding this field) - // * @+8 bytes pairs - // * @-24 bytes uint64: size in bytes (same as the one above) - // * @-16 bytes uint128: magic - ByteBuffer pairs = sliceFromTo(apkSigningBlock, 8, apkSigningBlock.capacity() - 24); - - int entryCount = 0; - while (pairs.hasRemaining()) { - entryCount++; - if (pairs.remaining() < 8) { - throw new SignatureNotFoundException( - "Insufficient data to read size of APK Signing Block entry #" + entryCount); - } - long lenLong = pairs.getLong(); - if ((lenLong < 4) || (lenLong > Integer.MAX_VALUE)) { - throw new SignatureNotFoundException( - "APK Signing Block entry #" + entryCount - + " size out of range: " + lenLong); - } - int len = (int) lenLong; - int nextEntryPos = pairs.position() + len; - if (len > pairs.remaining()) { - throw new SignatureNotFoundException( - "APK Signing Block entry #" + entryCount + " size out of range: " + len - + ", available: " + pairs.remaining()); - } - int id = pairs.getInt(); - if (id == blockId) { - return getByteBuffer(pairs, len - 4); - } - pairs.position(nextEntryPos); - } - - throw new SignatureNotFoundException( - "No APK Signature Scheme block in APK Signing Block with ID: " + blockId); - } - - public static void checkByteOrderLittleEndian(ByteBuffer buffer) { - if (buffer.order() != ByteOrder.LITTLE_ENDIAN) { - throw new IllegalArgumentException("ByteBuffer byte order must be little endian"); - } - } - - /** - * Returns the subset of signatures which are expected to be verified by at least one Android - * platform version in the {@code [minSdkVersion, maxSdkVersion]} range. The returned result is - * guaranteed to contain at least one signature. - * - *

Each Android platform version typically verifies exactly one signature from the provided - * {@code signatures} set. This method returns the set of these signatures collected over all - * requested platform versions. As a result, the result may contain more than one signature. - * - * @throws NoApkSupportedSignaturesException if no supported signatures were - * found for an Android platform version in the range. - */ - public static List getSignaturesToVerify( - List signatures, int minSdkVersion, int maxSdkVersion) - throws NoApkSupportedSignaturesException { - return getSignaturesToVerify(signatures, minSdkVersion, maxSdkVersion, false); - } - - /** - * Returns the subset of signatures which are expected to be verified by at least one Android - * platform version in the {@code [minSdkVersion, maxSdkVersion]} range. The returned result is - * guaranteed to contain at least one signature. - * - *

{@code onlyRequireJcaSupport} can be set to true for cases that only require verifying a - * signature within the signing block using the standard JCA. - * - *

Each Android platform version typically verifies exactly one signature from the provided - * {@code signatures} set. This method returns the set of these signatures collected over all - * requested platform versions. As a result, the result may contain more than one signature. - * - * @throws NoApkSupportedSignaturesException if no supported signatures were - * found for an Android platform version in the range. - */ - public static List getSignaturesToVerify( - List signatures, int minSdkVersion, int maxSdkVersion, - boolean onlyRequireJcaSupport) throws - NoApkSupportedSignaturesException { - // Pick the signature with the strongest algorithm at all required SDK versions, to mimic - // Android's behavior on those versions. - // - // Here we assume that, once introduced, a signature algorithm continues to be supported in - // all future Android versions. We also assume that the better-than relationship between - // algorithms is exactly the same on all Android platform versions (except that older - // platforms might support fewer algorithms). If these assumption are no longer true, the - // logic here will need to change accordingly. - Map - bestSigAlgorithmOnSdkVersion = new HashMap<>(); - int minProvidedSignaturesVersion = Integer.MAX_VALUE; - for (T sig : signatures) { - SignatureAlgorithm sigAlgorithm = sig.algorithm; - int sigMinSdkVersion = onlyRequireJcaSupport ? sigAlgorithm.getJcaSigAlgMinSdkVersion() - : sigAlgorithm.getMinSdkVersion(); - if (sigMinSdkVersion > maxSdkVersion) { - continue; - } - if (sigMinSdkVersion < minProvidedSignaturesVersion) { - minProvidedSignaturesVersion = sigMinSdkVersion; - } - - T candidate = bestSigAlgorithmOnSdkVersion.get(sigMinSdkVersion); - if ((candidate == null) - || (compareSignatureAlgorithm( - sigAlgorithm, candidate.algorithm) > 0)) { - bestSigAlgorithmOnSdkVersion.put(sigMinSdkVersion, sig); - } - } - - // Must have some supported signature algorithms for minSdkVersion. - if (minSdkVersion < minProvidedSignaturesVersion) { - throw new NoApkSupportedSignaturesException( - "Minimum provided signature version " + minProvidedSignaturesVersion + - " > minSdkVersion " + minSdkVersion); - } - if (bestSigAlgorithmOnSdkVersion.isEmpty()) { - throw new NoApkSupportedSignaturesException("No supported signature"); - } - List signaturesToVerify = - new ArrayList<>(bestSigAlgorithmOnSdkVersion.values()); - Collections.sort( - signaturesToVerify, - (sig1, sig2) -> Integer.compare(sig1.algorithm.getId(), sig2.algorithm.getId())); - return signaturesToVerify; - } - - /** - * 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. - */ - public static int compareSignatureAlgorithm(SignatureAlgorithm alg1, SignatureAlgorithm alg2) { - ContentDigestAlgorithm digestAlg1 = alg1.getContentDigestAlgorithm(); - ContentDigestAlgorithm digestAlg2 = alg2.getContentDigestAlgorithm(); - return compareContentDigestAlgorithm(digestAlg1, digestAlg2); - } - - /** - * Returns a positive number if {@code alg1} is preferred over {@code alg2}, a negative number - * if {@code alg2} is preferred over {@code alg1}, or {@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: - case VERITY_CHUNKED_SHA256: - return -1; - default: - throw new IllegalArgumentException("Unknown alg2: " + alg2); - } - case CHUNKED_SHA512: - switch (alg2) { - case CHUNKED_SHA256: - case VERITY_CHUNKED_SHA256: - return 1; - case CHUNKED_SHA512: - return 0; - default: - throw new IllegalArgumentException("Unknown alg2: " + alg2); - } - case VERITY_CHUNKED_SHA256: - switch (alg2) { - case CHUNKED_SHA256: - return 1; - case VERITY_CHUNKED_SHA256: - return 0; - case CHUNKED_SHA512: - return -1; - default: - throw new IllegalArgumentException("Unknown alg2: " + alg2); - } - default: - throw new IllegalArgumentException("Unknown alg1: " + alg1); - } - } - - /** - * 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 get method for reading {@code size} number of bytes from the current - * position of this buffer. - * - *

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) { - 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); - } - } - - public 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 ByteBuffer getLengthPrefixedSlice(ByteBuffer source) throws ApkFormatException { - if (source.remaining() < 4) { - throw new ApkFormatException( - "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 ApkFormatException( - "Length-prefixed field longer than remaining buffer" - + ". Field length: " + len + ", remaining: " + source.remaining()); - } - return getByteBuffer(source, len); - } - - public static byte[] readLengthPrefixedByteArray(ByteBuffer buf) throws ApkFormatException { - int len = buf.getInt(); - if (len < 0) { - throw new ApkFormatException("Negative length"); - } else if (len > buf.remaining()) { - throw new ApkFormatException( - "Underflow while reading length-prefixed value. Length: " + len - + ", available: " + buf.remaining()); - } - byte[] result = new byte[len]; - buf.get(result); - return result; - } - - public static byte[] encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes( - List> sequence) { - int resultSize = 0; - for (Pair element : sequence) { - resultSize += 12 + element.getSecond().length; - } - ByteBuffer result = ByteBuffer.allocate(resultSize); - result.order(ByteOrder.LITTLE_ENDIAN); - for (Pair 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/app/src/main/java/com/android/apksig/internal/apk/ApkSupportedSignature.java b/app/src/main/java/com/android/apksig/internal/apk/ApkSupportedSignature.java deleted file mode 100644 index 61652a435e..0000000000 --- a/app/src/main/java/com/android/apksig/internal/apk/ApkSupportedSignature.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.apk; - -/** - * Base implementation of a supported signature for an APK. - */ -public class ApkSupportedSignature { - public final SignatureAlgorithm algorithm; - public final byte[] signature; - - /** - * Constructs a new supported signature using the provided {@code algorithm} and {@code - * signature} bytes. - */ - public ApkSupportedSignature(SignatureAlgorithm algorithm, byte[] signature) { - this.algorithm = algorithm; - this.signature = signature; - } - -} diff --git a/app/src/main/java/com/android/apksig/internal/apk/ContentDigestAlgorithm.java b/app/src/main/java/com/android/apksig/internal/apk/ContentDigestAlgorithm.java deleted file mode 100644 index b806d1e420..0000000000 --- a/app/src/main/java/com/android/apksig/internal/apk/ContentDigestAlgorithm.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.apksig.internal.apk; - -/** APK Signature Scheme v2 content digest algorithm. */ -public enum ContentDigestAlgorithm { - /** SHA2-256 over 1 MB chunks. */ - CHUNKED_SHA256(1, "SHA-256", 256 / 8), - - /** SHA2-512 over 1 MB chunks. */ - CHUNKED_SHA512(2, "SHA-512", 512 / 8), - - /** SHA2-256 over 4 KB chunks for APK verity. */ - VERITY_CHUNKED_SHA256(3, "SHA-256", 256 / 8), - - /** Non-chunk SHA2-256. */ - SHA256(4, "SHA-256", 256 / 8); - - private final int mId; - private final String mJcaMessageDigestAlgorithm; - private final int mChunkDigestOutputSizeBytes; - - private ContentDigestAlgorithm( - int id, String jcaMessageDigestAlgorithm, int chunkDigestOutputSizeBytes) { - mId = id; - mJcaMessageDigestAlgorithm = jcaMessageDigestAlgorithm; - mChunkDigestOutputSizeBytes = chunkDigestOutputSizeBytes; - } - - /** Returns the ID of the digest algorithm used on the APK. */ - public int getId() { - return mId; - } - - /** - * 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; - } -} diff --git a/app/src/main/java/com/android/apksig/internal/apk/NoApkSupportedSignaturesException.java b/app/src/main/java/com/android/apksig/internal/apk/NoApkSupportedSignaturesException.java deleted file mode 100644 index 52c6085c5f..0000000000 --- a/app/src/main/java/com/android/apksig/internal/apk/NoApkSupportedSignaturesException.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.apk; - -/** - * Base exception that is thrown when there are no signatures that support the full range of - * requested platform versions. - */ -public class NoApkSupportedSignaturesException extends Exception { - public NoApkSupportedSignaturesException(String message) { - super(message); - } -} diff --git a/app/src/main/java/com/android/apksig/internal/apk/SignatureAlgorithm.java b/app/src/main/java/com/android/apksig/internal/apk/SignatureAlgorithm.java deleted file mode 100644 index 804eb37bdd..0000000000 --- a/app/src/main/java/com/android/apksig/internal/apk/SignatureAlgorithm.java +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.apk; - -import com.android.apksig.internal.util.AndroidSdkVersion; -import com.android.apksig.internal.util.Pair; -import java.security.spec.AlgorithmParameterSpec; -import java.security.spec.MGF1ParameterSpec; -import java.security.spec.PSSParameterSpec; - -/** - * APK Signing Block signature algorithm. - */ -public enum SignatureAlgorithm { - // TODO reserve the 0x0000 ID to mean null - /** - * 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)), - AndroidSdkVersion.N, - AndroidSdkVersion.M), - - /** - * 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)), - AndroidSdkVersion.N, - AndroidSdkVersion.M), - - /** 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), - AndroidSdkVersion.N, - AndroidSdkVersion.INITIAL_RELEASE), - - /** 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), - AndroidSdkVersion.N, - AndroidSdkVersion.INITIAL_RELEASE), - - /** 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), - AndroidSdkVersion.N, - AndroidSdkVersion.HONEYCOMB), - - /** 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), - AndroidSdkVersion.N, - AndroidSdkVersion.HONEYCOMB), - - /** 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), - AndroidSdkVersion.N, - AndroidSdkVersion.INITIAL_RELEASE), - - /** - * DSA with SHA2-256 digest, content digested using SHA2-256 in 1 MB chunks. Signing is done - * deterministically according to RFC 6979. - */ - DETDSA_WITH_SHA256( - 0x0301, - ContentDigestAlgorithm.CHUNKED_SHA256, - "DSA", - Pair.of("SHA256withDetDSA", null), - AndroidSdkVersion.N, - AndroidSdkVersion.INITIAL_RELEASE), - - /** - * RSASSA-PKCS1-v1_5 with SHA2-256 digest, content digested using SHA2-256 in 4 KB chunks, in - * the same way fsverity operates. This digest and the content length (before digestion, 8 bytes - * in little endian) construct the final digest. - */ - VERITY_RSA_PKCS1_V1_5_WITH_SHA256( - 0x0421, - ContentDigestAlgorithm.VERITY_CHUNKED_SHA256, - "RSA", - Pair.of("SHA256withRSA", null), - AndroidSdkVersion.P, - AndroidSdkVersion.INITIAL_RELEASE), - - /** - * ECDSA with SHA2-256 digest, content digested using SHA2-256 in 4 KB chunks, in the same way - * fsverity operates. This digest and the content length (before digestion, 8 bytes in little - * endian) construct the final digest. - */ - VERITY_ECDSA_WITH_SHA256( - 0x0423, - ContentDigestAlgorithm.VERITY_CHUNKED_SHA256, - "EC", - Pair.of("SHA256withECDSA", null), - AndroidSdkVersion.P, - AndroidSdkVersion.HONEYCOMB), - - /** - * DSA with SHA2-256 digest, content digested using SHA2-256 in 4 KB chunks, in the same way - * fsverity operates. This digest and the content length (before digestion, 8 bytes in little - * endian) construct the final digest. - */ - VERITY_DSA_WITH_SHA256( - 0x0425, - ContentDigestAlgorithm.VERITY_CHUNKED_SHA256, - "DSA", - Pair.of("SHA256withDSA", null), - AndroidSdkVersion.P, - AndroidSdkVersion.INITIAL_RELEASE); - - private final int mId; - private final String mJcaKeyAlgorithm; - private final ContentDigestAlgorithm mContentDigestAlgorithm; - private final Pair mJcaSignatureAlgAndParams; - private final int mMinSdkVersion; - private final int mJcaSigAlgMinSdkVersion; - - SignatureAlgorithm(int id, - ContentDigestAlgorithm contentDigestAlgorithm, - String jcaKeyAlgorithm, - Pair jcaSignatureAlgAndParams, - int minSdkVersion, - int jcaSigAlgMinSdkVersion) { - mId = id; - mContentDigestAlgorithm = contentDigestAlgorithm; - mJcaKeyAlgorithm = jcaKeyAlgorithm; - mJcaSignatureAlgAndParams = jcaSignatureAlgAndParams; - mMinSdkVersion = minSdkVersion; - mJcaSigAlgMinSdkVersion = jcaSigAlgMinSdkVersion; - } - - /** - * Returns the ID of this signature algorithm as used in APK Signature Scheme v2 wire format. - */ - public int getId() { - return mId; - } - - /** - * Returns the content digest algorithm associated with this signature algorithm. - */ - public ContentDigestAlgorithm getContentDigestAlgorithm() { - return mContentDigestAlgorithm; - } - - /** - * Returns the JCA {@link java.security.Key} algorithm used by this signature scheme. - */ - public 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}. - */ - public Pair getJcaSignatureAlgorithmAndParams() { - return mJcaSignatureAlgAndParams; - } - - public int getMinSdkVersion() { - return mMinSdkVersion; - } - - /** - * Returns the minimum SDK version that supports the JCA signature algorithm. - */ - public int getJcaSigAlgMinSdkVersion() { - return mJcaSigAlgMinSdkVersion; - } - - public static SignatureAlgorithm findById(int id) { - for (SignatureAlgorithm alg : SignatureAlgorithm.values()) { - if (alg.getId() == id) { - return alg; - } - } - - return null; - } -} diff --git a/app/src/main/java/com/android/apksig/internal/apk/SignatureInfo.java b/app/src/main/java/com/android/apksig/internal/apk/SignatureInfo.java deleted file mode 100644 index 5e26327b8d..0000000000 --- a/app/src/main/java/com/android/apksig/internal/apk/SignatureInfo.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.apk; - -import java.nio.ByteBuffer; - -/** - * APK Signature Scheme block and additional information relevant to verifying the signatures - * contained in the block against the file. - */ -public class SignatureInfo { - /** Contents of APK Signature Scheme block. */ - public final ByteBuffer signatureBlock; - - /** Position of the APK Signing Block in the file. */ - public final long apkSigningBlockOffset; - - /** Position of the ZIP Central Directory in the file. */ - public final long centralDirOffset; - - /** Position of the ZIP End of Central Directory (EoCD) in the file. */ - public final long eocdOffset; - - /** Contents of ZIP End of Central Directory (EoCD) of the file. */ - public final ByteBuffer eocd; - - public 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; - } -} diff --git a/app/src/main/java/com/android/apksig/internal/apk/SignatureNotFoundException.java b/app/src/main/java/com/android/apksig/internal/apk/SignatureNotFoundException.java deleted file mode 100644 index 95f06eff8a..0000000000 --- a/app/src/main/java/com/android/apksig/internal/apk/SignatureNotFoundException.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.apk; - -/** - * Base exception that is thrown when the APK is not signed with the requested signature scheme. - */ -public class SignatureNotFoundException extends Exception { - public SignatureNotFoundException(String message) { - super(message); - } - - public SignatureNotFoundException(String message, Throwable cause) { - super(message, cause); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/android/apksig/internal/apk/stamp/SourceStampCertificateLineage.java b/app/src/main/java/com/android/apksig/internal/apk/stamp/SourceStampCertificateLineage.java deleted file mode 100644 index 93627ff0e3..0000000000 --- a/app/src/main/java/com/android/apksig/internal/apk/stamp/SourceStampCertificateLineage.java +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.apk.stamp; - -import static com.android.apksig.internal.apk.ApkSigningBlockUtilsLite.getLengthPrefixedSlice; -import static com.android.apksig.internal.apk.ApkSigningBlockUtilsLite.readLengthPrefixedByteArray; - -import com.android.apksig.apk.ApkFormatException; -import com.android.apksig.internal.apk.ApkSigningBlockUtilsLite; -import com.android.apksig.internal.apk.SignatureAlgorithm; -import com.android.apksig.internal.util.GuaranteedEncodedFormX509Certificate; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.nio.BufferUnderflowException; -import java.nio.ByteBuffer; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; -import java.security.Signature; -import java.security.SignatureException; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.security.spec.AlgorithmParameterSpec; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; - -/** Lightweight version of the V3SigningCertificateLineage to be used for source stamps. */ -public class SourceStampCertificateLineage { - - private final static int FIRST_VERSION = 1; - private final static int CURRENT_VERSION = FIRST_VERSION; - - /** - * Deserializes the binary representation of a SourceStampCertificateLineage. Also - * verifies that the structure is well-formed, e.g. that the signature for each node is from its - * parent. - */ - public static List readSigningCertificateLineage(ByteBuffer inputBytes) - throws IOException { - List result = new ArrayList<>(); - int nodeCount = 0; - if (inputBytes == null || !inputBytes.hasRemaining()) { - return null; - } - - ApkSigningBlockUtilsLite.checkByteOrderLittleEndian(inputBytes); - - CertificateFactory certFactory; - try { - certFactory = CertificateFactory.getInstance("X.509"); - } catch (CertificateException e) { - throw new IllegalStateException("Failed to obtain X.509 CertificateFactory", e); - } - - // FORMAT (little endian): - // * uint32: version code - // * sequence of length-prefixed (uint32): nodes - // * length-prefixed bytes: signed data - // * length-prefixed bytes: certificate - // * uint32: signature algorithm id - // * uint32: flags - // * uint32: signature algorithm id (used by to sign next cert in lineage) - // * length-prefixed bytes: signature over above signed data - - X509Certificate lastCert = null; - int lastSigAlgorithmId = 0; - - try { - int version = inputBytes.getInt(); - if (version != CURRENT_VERSION) { - // we only have one version to worry about right now, so just check it - throw new IllegalArgumentException("Encoded SigningCertificateLineage has a version" - + " different than any of which we are aware"); - } - HashSet certHistorySet = new HashSet<>(); - while (inputBytes.hasRemaining()) { - nodeCount++; - ByteBuffer nodeBytes = getLengthPrefixedSlice(inputBytes); - ByteBuffer signedData = getLengthPrefixedSlice(nodeBytes); - int flags = nodeBytes.getInt(); - int sigAlgorithmId = nodeBytes.getInt(); - SignatureAlgorithm sigAlgorithm = SignatureAlgorithm.findById(lastSigAlgorithmId); - byte[] signature = readLengthPrefixedByteArray(nodeBytes); - - if (lastCert != null) { - // Use previous level cert to verify current level - String jcaSignatureAlgorithm = - sigAlgorithm.getJcaSignatureAlgorithmAndParams().getFirst(); - AlgorithmParameterSpec jcaSignatureAlgorithmParams = - sigAlgorithm.getJcaSignatureAlgorithmAndParams().getSecond(); - PublicKey publicKey = lastCert.getPublicKey(); - Signature sig = Signature.getInstance(jcaSignatureAlgorithm); - sig.initVerify(publicKey); - if (jcaSignatureAlgorithmParams != null) { - sig.setParameter(jcaSignatureAlgorithmParams); - } - sig.update(signedData); - if (!sig.verify(signature)) { - throw new SecurityException("Unable to verify signature of certificate #" - + nodeCount + " using " + jcaSignatureAlgorithm + " when verifying" - + " SourceStampCertificateLineage object"); - } - } - - signedData.rewind(); - byte[] encodedCert = readLengthPrefixedByteArray(signedData); - int signedSigAlgorithm = signedData.getInt(); - if (lastCert != null && lastSigAlgorithmId != signedSigAlgorithm) { - throw new SecurityException("Signing algorithm ID mismatch for certificate #" - + nodeBytes + " when verifying SourceStampCertificateLineage object"); - } - lastCert = (X509Certificate) certFactory.generateCertificate( - new ByteArrayInputStream(encodedCert)); - lastCert = new GuaranteedEncodedFormX509Certificate(lastCert, encodedCert); - if (certHistorySet.contains(lastCert)) { - throw new SecurityException("Encountered duplicate entries in " - + "SigningCertificateLineage at certificate #" + nodeCount + ". All " - + "signing certificates should be unique"); - } - certHistorySet.add(lastCert); - lastSigAlgorithmId = sigAlgorithmId; - result.add(new SigningCertificateNode( - lastCert, SignatureAlgorithm.findById(signedSigAlgorithm), - SignatureAlgorithm.findById(sigAlgorithmId), signature, flags)); - } - } catch(ApkFormatException | BufferUnderflowException e){ - throw new IOException("Failed to parse SourceStampCertificateLineage object", e); - } catch(NoSuchAlgorithmException | InvalidKeyException - | InvalidAlgorithmParameterException | SignatureException e){ - throw new SecurityException( - "Failed to verify signature over signed data for certificate #" + nodeCount - + " when parsing SourceStampCertificateLineage object", e); - } catch(CertificateException e){ - throw new SecurityException("Failed to decode certificate #" + nodeCount - + " when parsing SourceStampCertificateLineage object", e); - } - return result; - } - - /** - * Represents one signing certificate in the SourceStampCertificateLineage, which - * generally means it is/was used at some point to sign source stamps. - */ - public static class SigningCertificateNode { - - public SigningCertificateNode( - X509Certificate signingCert, - SignatureAlgorithm parentSigAlgorithm, - SignatureAlgorithm sigAlgorithm, - byte[] signature, - int flags) { - this.signingCert = signingCert; - this.parentSigAlgorithm = parentSigAlgorithm; - this.sigAlgorithm = sigAlgorithm; - this.signature = signature; - this.flags = flags; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof SigningCertificateNode)) return false; - - SigningCertificateNode that = (SigningCertificateNode) o; - if (!signingCert.equals(that.signingCert)) return false; - if (parentSigAlgorithm != that.parentSigAlgorithm) return false; - if (sigAlgorithm != that.sigAlgorithm) return false; - if (!Arrays.equals(signature, that.signature)) return false; - if (flags != that.flags) return false; - - // we made it - return true; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((signingCert == null) ? 0 : signingCert.hashCode()); - result = prime * result + - ((parentSigAlgorithm == null) ? 0 : parentSigAlgorithm.hashCode()); - result = prime * result + ((sigAlgorithm == null) ? 0 : sigAlgorithm.hashCode()); - result = prime * result + Arrays.hashCode(signature); - result = prime * result + flags; - return result; - } - - /** - * the signing cert for this node. This is part of the data signed by the parent node. - */ - public final X509Certificate signingCert; - - /** - * the algorithm used by this node's parent to bless this data. Its ID value is part of - * the data signed by the parent node. {@code null} for first node. - */ - public final SignatureAlgorithm parentSigAlgorithm; - - /** - * the algorithm used by this node to bless the next node's data. Its ID value is part - * of the signed data of the next node. {@code null} for the last node. - */ - public SignatureAlgorithm sigAlgorithm; - - /** - * signature over the signed data (above). The signature is from this node's parent - * signing certificate, which should correspond to the signing certificate used to sign an - * APK before rotating to this one, and is formed using {@code signatureAlgorithm}. - */ - public final byte[] signature; - - /** - * the flags detailing how the platform should treat this signing cert - */ - public int flags; - } -} diff --git a/app/src/main/java/com/android/apksig/internal/apk/stamp/SourceStampConstants.java b/app/src/main/java/com/android/apksig/internal/apk/stamp/SourceStampConstants.java deleted file mode 100644 index 465fbb0595..0000000000 --- a/app/src/main/java/com/android/apksig/internal/apk/stamp/SourceStampConstants.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.apk.stamp; - -/** Constants used for source stamp signing and verification. */ -public class SourceStampConstants { - private SourceStampConstants() {} - - public static final int V1_SOURCE_STAMP_BLOCK_ID = 0x2b09189e; - public static final int V2_SOURCE_STAMP_BLOCK_ID = 0x6dff800d; - public static final String SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME = "stamp-cert-sha256"; - public static final int PROOF_OF_ROTATION_ATTR_ID = 0x9d6303f7; -} diff --git a/app/src/main/java/com/android/apksig/internal/apk/stamp/SourceStampVerifier.java b/app/src/main/java/com/android/apksig/internal/apk/stamp/SourceStampVerifier.java deleted file mode 100644 index b4ae71a20c..0000000000 --- a/app/src/main/java/com/android/apksig/internal/apk/stamp/SourceStampVerifier.java +++ /dev/null @@ -1,348 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.apksig.internal.apk.stamp; - -import static com.android.apksig.internal.apk.ApkSigningBlockUtilsLite.getLengthPrefixedSlice; -import static com.android.apksig.internal.apk.ApkSigningBlockUtilsLite.getSignaturesToVerify; -import static com.android.apksig.internal.apk.ApkSigningBlockUtilsLite.readLengthPrefixedByteArray; -import static com.android.apksig.internal.apk.ApkSigningBlockUtilsLite.toHex; - -import com.android.apksig.ApkVerificationIssue; -import com.android.apksig.apk.ApkFormatException; -import com.android.apksig.internal.apk.ApkSignerInfo; -import com.android.apksig.internal.apk.ApkSupportedSignature; -import com.android.apksig.internal.apk.NoApkSupportedSignaturesException; -import com.android.apksig.internal.apk.SignatureAlgorithm; -import com.android.apksig.internal.apk.v3.V3SigningCertificateLineage; -import com.android.apksig.internal.util.ByteBufferUtils; -import com.android.apksig.internal.util.GuaranteedEncodedFormX509Certificate; - -import java.io.ByteArrayInputStream; -import java.nio.BufferUnderflowException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; -import java.security.Signature; -import java.security.SignatureException; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.security.spec.AlgorithmParameterSpec; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Source Stamp verifier. - * - *

SourceStamp improves traceability of apps with respect to unauthorized distribution. - * - *

The stamp is part of the APK that is protected by the signing block. - * - *

The APK contents hash is signed using the stamp key, and is saved as part of the signing - * block. - */ -class SourceStampVerifier { - /** Hidden constructor to prevent instantiation. */ - private SourceStampVerifier() { - } - - /** - * Parses the SourceStamp block and populates the {@code result}. - * - *

This verifies signatures over digest provided. - * - *

This method adds one or more errors to the {@code result} if a verification error is - * expected to be encountered on an Android platform version in the {@code [minSdkVersion, - * maxSdkVersion]} range. - */ - public static void verifyV1SourceStamp( - ByteBuffer sourceStampBlockData, - CertificateFactory certFactory, - ApkSignerInfo result, - byte[] apkDigest, - byte[] sourceStampCertificateDigest, - int minSdkVersion, - int maxSdkVersion) - throws ApkFormatException, NoSuchAlgorithmException { - X509Certificate sourceStampCertificate = - verifySourceStampCertificate( - sourceStampBlockData, certFactory, sourceStampCertificateDigest, result); - if (result.containsWarnings() || result.containsErrors()) { - return; - } - - ByteBuffer apkDigestSignatures = getLengthPrefixedSlice(sourceStampBlockData); - verifySourceStampSignature( - apkDigest, - minSdkVersion, - maxSdkVersion, - sourceStampCertificate, - apkDigestSignatures, - result); - } - - /** - * Parses the SourceStamp block and populates the {@code result}. - * - *

This verifies signatures over digest of multiple signature schemes provided. - * - *

This method adds one or more errors to the {@code result} if a verification error is - * expected to be encountered on an Android platform version in the {@code [minSdkVersion, - * maxSdkVersion]} range. - */ - public static void verifyV2SourceStamp( - ByteBuffer sourceStampBlockData, - CertificateFactory certFactory, - ApkSignerInfo result, - Map signatureSchemeApkDigests, - byte[] sourceStampCertificateDigest, - int minSdkVersion, - int maxSdkVersion) - throws ApkFormatException, NoSuchAlgorithmException { - X509Certificate sourceStampCertificate = - verifySourceStampCertificate( - sourceStampBlockData, certFactory, sourceStampCertificateDigest, result); - if (result.containsWarnings() || result.containsErrors()) { - return; - } - - // Parse signed signature schemes block. - ByteBuffer signedSignatureSchemes = getLengthPrefixedSlice(sourceStampBlockData); - Map signedSignatureSchemeData = new HashMap<>(); - while (signedSignatureSchemes.hasRemaining()) { - ByteBuffer signedSignatureScheme = getLengthPrefixedSlice(signedSignatureSchemes); - int signatureSchemeId = signedSignatureScheme.getInt(); - ByteBuffer apkDigestSignatures = getLengthPrefixedSlice(signedSignatureScheme); - signedSignatureSchemeData.put(signatureSchemeId, apkDigestSignatures); - } - - for (Map.Entry signatureSchemeApkDigest : - signatureSchemeApkDigests.entrySet()) { - if (!signedSignatureSchemeData.containsKey(signatureSchemeApkDigest.getKey())) { - result.addWarning(ApkVerificationIssue.SOURCE_STAMP_NO_SIGNATURE); - return; - } - verifySourceStampSignature( - signatureSchemeApkDigest.getValue(), - minSdkVersion, - maxSdkVersion, - sourceStampCertificate, - signedSignatureSchemeData.get(signatureSchemeApkDigest.getKey()), - result); - if (result.containsWarnings() || result.containsErrors()) { - return; - } - } - - if (sourceStampBlockData.hasRemaining()) { - // The stamp block contains some additional attributes. - ByteBuffer stampAttributeData = getLengthPrefixedSlice(sourceStampBlockData); - ByteBuffer stampAttributeDataSignatures = getLengthPrefixedSlice(sourceStampBlockData); - - byte[] stampAttributeBytes = new byte[stampAttributeData.remaining()]; - stampAttributeData.get(stampAttributeBytes); - stampAttributeData.flip(); - - verifySourceStampSignature(stampAttributeBytes, minSdkVersion, maxSdkVersion, - sourceStampCertificate, stampAttributeDataSignatures, result); - if (result.containsErrors() || result.containsWarnings()) { - return; - } - parseStampAttributes(stampAttributeData, sourceStampCertificate, result); - } - } - - private static X509Certificate verifySourceStampCertificate( - ByteBuffer sourceStampBlockData, - CertificateFactory certFactory, - byte[] sourceStampCertificateDigest, - ApkSignerInfo result) - throws NoSuchAlgorithmException, ApkFormatException { - // Parse the SourceStamp certificate. - byte[] sourceStampEncodedCertificate = readLengthPrefixedByteArray(sourceStampBlockData); - X509Certificate sourceStampCertificate; - try { - sourceStampCertificate = (X509Certificate) certFactory.generateCertificate( - new ByteArrayInputStream(sourceStampEncodedCertificate)); - } catch (CertificateException e) { - result.addWarning(ApkVerificationIssue.SOURCE_STAMP_MALFORMED_CERTIFICATE, e); - return null; - } - // Wrap the cert so that the result's getEncoded returns exactly the original encoded - // form. Without this, getEncoded may return a different form from what was stored in - // the signature. This is because some X509Certificate(Factory) implementations - // re-encode certificates. - sourceStampCertificate = - new GuaranteedEncodedFormX509Certificate( - sourceStampCertificate, sourceStampEncodedCertificate); - result.certs.add(sourceStampCertificate); - // Verify the SourceStamp certificate found in the signing block is the same as the - // SourceStamp certificate found in the APK. - MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); - messageDigest.update(sourceStampEncodedCertificate); - byte[] sourceStampBlockCertificateDigest = messageDigest.digest(); - if (!Arrays.equals(sourceStampCertificateDigest, sourceStampBlockCertificateDigest)) { - result.addWarning( - ApkVerificationIssue - .SOURCE_STAMP_CERTIFICATE_MISMATCH_BETWEEN_SIGNATURE_BLOCK_AND_APK, - toHex(sourceStampBlockCertificateDigest), - toHex(sourceStampCertificateDigest)); - return null; - } - return sourceStampCertificate; - } - - private static void verifySourceStampSignature( - byte[] data, - int minSdkVersion, - int maxSdkVersion, - X509Certificate sourceStampCertificate, - ByteBuffer signatures, - ApkSignerInfo result) { - // Parse the signatures block and identify supported signatures - int signatureCount = 0; - List supportedSignatures = new ArrayList<>(1); - while (signatures.hasRemaining()) { - signatureCount++; - try { - ByteBuffer signature = getLengthPrefixedSlice(signatures); - int sigAlgorithmId = signature.getInt(); - byte[] sigBytes = readLengthPrefixedByteArray(signature); - SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.findById(sigAlgorithmId); - if (signatureAlgorithm == null) { - result.addWarning( - ApkVerificationIssue.SOURCE_STAMP_UNKNOWN_SIG_ALGORITHM, - sigAlgorithmId); - continue; - } - supportedSignatures.add( - new ApkSupportedSignature(signatureAlgorithm, sigBytes)); - } catch (ApkFormatException | BufferUnderflowException e) { - result.addWarning( - ApkVerificationIssue.SOURCE_STAMP_MALFORMED_SIGNATURE, signatureCount); - return; - } - } - if (supportedSignatures.isEmpty()) { - result.addWarning(ApkVerificationIssue.SOURCE_STAMP_NO_SIGNATURE); - return; - } - // Verify signatures over digests using the SourceStamp's certificate. - List signaturesToVerify; - try { - signaturesToVerify = - getSignaturesToVerify( - supportedSignatures, minSdkVersion, maxSdkVersion, true); - } catch (NoApkSupportedSignaturesException e) { - // To facilitate debugging capture the signature algorithms and resulting exception in - // the warning. - StringBuilder signatureAlgorithms = new StringBuilder(); - for (ApkSupportedSignature supportedSignature : supportedSignatures) { - if (signatureAlgorithms.length() > 0) { - signatureAlgorithms.append(", "); - } - signatureAlgorithms.append(supportedSignature.algorithm); - } - result.addWarning(ApkVerificationIssue.SOURCE_STAMP_NO_SUPPORTED_SIGNATURE, - signatureAlgorithms.toString(), e); - return; - } - for (ApkSupportedSignature signature : signaturesToVerify) { - SignatureAlgorithm signatureAlgorithm = signature.algorithm; - String jcaSignatureAlgorithm = - signatureAlgorithm.getJcaSignatureAlgorithmAndParams().getFirst(); - AlgorithmParameterSpec jcaSignatureAlgorithmParams = - signatureAlgorithm.getJcaSignatureAlgorithmAndParams().getSecond(); - PublicKey publicKey = sourceStampCertificate.getPublicKey(); - try { - Signature sig = Signature.getInstance(jcaSignatureAlgorithm); - sig.initVerify(publicKey); - if (jcaSignatureAlgorithmParams != null) { - sig.setParameter(jcaSignatureAlgorithmParams); - } - sig.update(data); - byte[] sigBytes = signature.signature; - if (!sig.verify(sigBytes)) { - result.addWarning( - ApkVerificationIssue.SOURCE_STAMP_DID_NOT_VERIFY, signatureAlgorithm); - return; - } - } catch (InvalidKeyException - | InvalidAlgorithmParameterException - | SignatureException - | NoSuchAlgorithmException e) { - result.addWarning( - ApkVerificationIssue.SOURCE_STAMP_VERIFY_EXCEPTION, signatureAlgorithm, e); - return; - } - } - } - - private static void parseStampAttributes(ByteBuffer stampAttributeData, - X509Certificate sourceStampCertificate, ApkSignerInfo result) - throws ApkFormatException { - ByteBuffer stampAttributes = getLengthPrefixedSlice(stampAttributeData); - int stampAttributeCount = 0; - while (stampAttributes.hasRemaining()) { - stampAttributeCount++; - try { - ByteBuffer attribute = getLengthPrefixedSlice(stampAttributes); - int id = attribute.getInt(); - byte[] value = ByteBufferUtils.toByteArray(attribute); - if (id == SourceStampConstants.PROOF_OF_ROTATION_ATTR_ID) { - readStampCertificateLineage(value, sourceStampCertificate, result); - } else { - result.addWarning(ApkVerificationIssue.SOURCE_STAMP_UNKNOWN_ATTRIBUTE, id); - } - } catch (ApkFormatException | BufferUnderflowException e) { - result.addWarning(ApkVerificationIssue.SOURCE_STAMP_MALFORMED_ATTRIBUTE, - stampAttributeCount); - return; - } - } - } - - private static void readStampCertificateLineage(byte[] lineageBytes, - X509Certificate sourceStampCertificate, ApkSignerInfo result) { - try { - // SourceStampCertificateLineage is verified when built - List nodes = - SourceStampCertificateLineage.readSigningCertificateLineage( - ByteBuffer.wrap(lineageBytes).order(ByteOrder.LITTLE_ENDIAN)); - for (int i = 0; i < nodes.size(); i++) { - result.certificateLineage.add(nodes.get(i).signingCert); - } - // Make sure that the last cert in the chain matches this signer cert - if (!sourceStampCertificate.equals( - result.certificateLineage.get(result.certificateLineage.size() - 1))) { - result.addWarning(ApkVerificationIssue.SOURCE_STAMP_POR_CERT_MISMATCH); - } - } catch (SecurityException e) { - result.addWarning(ApkVerificationIssue.SOURCE_STAMP_POR_DID_NOT_VERIFY); - } catch (IllegalArgumentException e) { - result.addWarning(ApkVerificationIssue.SOURCE_STAMP_POR_CERT_MISMATCH); - } catch (Exception e) { - result.addWarning(ApkVerificationIssue.SOURCE_STAMP_MALFORMED_LINEAGE); - } - } -} diff --git a/app/src/main/java/com/android/apksig/internal/apk/stamp/V1SourceStampSigner.java b/app/src/main/java/com/android/apksig/internal/apk/stamp/V1SourceStampSigner.java deleted file mode 100644 index dee24bd1f6..0000000000 --- a/app/src/main/java/com/android/apksig/internal/apk/stamp/V1SourceStampSigner.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.apk.stamp; - -import static com.android.apksig.internal.apk.ApkSigningBlockUtils.encodeAsLengthPrefixedElement; -import static com.android.apksig.internal.apk.ApkSigningBlockUtils.encodeAsSequenceOfLengthPrefixedElements; -import static com.android.apksig.internal.apk.ApkSigningBlockUtils.encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes; - -import com.android.apksig.internal.apk.ApkSigningBlockUtils; -import com.android.apksig.internal.apk.ApkSigningBlockUtils.SignerConfig; -import com.android.apksig.internal.apk.ContentDigestAlgorithm; -import com.android.apksig.internal.util.Pair; - -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.SignatureException; -import java.security.cert.CertificateEncodingException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Map; - -/** - * SourceStamp signer. - * - *

SourceStamp improves traceability of apps with respect to unauthorized distribution. - * - *

The stamp is part of the APK that is protected by the signing block. - * - *

The APK contents hash is signed using the stamp key, and is saved as part of the signing - * block. - * - *

V1 of the source stamp allows signing the digest of at most one signature scheme only. - */ -public abstract class V1SourceStampSigner { - public static final int V1_SOURCE_STAMP_BLOCK_ID = - SourceStampConstants.V1_SOURCE_STAMP_BLOCK_ID; - - /** Hidden constructor to prevent instantiation. */ - private V1SourceStampSigner() {} - - public static Pair generateSourceStampBlock( - SignerConfig sourceStampSignerConfig, Map digestInfo) - throws SignatureException, NoSuchAlgorithmException, InvalidKeyException { - if (sourceStampSignerConfig.certificates.isEmpty()) { - throw new SignatureException("No certificates configured for signer"); - } - - List> digests = new ArrayList<>(); - for (Map.Entry digest : digestInfo.entrySet()) { - digests.add(Pair.of(digest.getKey().getId(), digest.getValue())); - } - Collections.sort(digests, Comparator.comparing(Pair::getFirst)); - - SourceStampBlock sourceStampBlock = new SourceStampBlock(); - - try { - sourceStampBlock.stampCertificate = - sourceStampSignerConfig.certificates.get(0).getEncoded(); - } catch (CertificateEncodingException e) { - throw new SignatureException( - "Retrieving the encoded form of the stamp certificate failed", e); - } - - byte[] digestBytes = - encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes(digests); - sourceStampBlock.signedDigests = - ApkSigningBlockUtils.generateSignaturesOverData( - sourceStampSignerConfig, digestBytes); - - // FORMAT: - // * length-prefixed bytes: X.509 certificate (ASN.1 DER encoded) - // * length-prefixed sequence of length-prefixed signatures: - // * uint32: signature algorithm ID - // * length-prefixed bytes: signature of signed data - byte[] sourceStampSignerBlock = - encodeAsSequenceOfLengthPrefixedElements( - new byte[][] { - sourceStampBlock.stampCertificate, - encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes( - sourceStampBlock.signedDigests), - }); - - // FORMAT: - // * length-prefixed stamp block. - return Pair.of(encodeAsLengthPrefixedElement(sourceStampSignerBlock), - SourceStampConstants.V1_SOURCE_STAMP_BLOCK_ID); - } - - private static final class SourceStampBlock { - public byte[] stampCertificate; - public List> signedDigests; - } -} diff --git a/app/src/main/java/com/android/apksig/internal/apk/stamp/V1SourceStampVerifier.java b/app/src/main/java/com/android/apksig/internal/apk/stamp/V1SourceStampVerifier.java deleted file mode 100644 index c3fdeecc7b..0000000000 --- a/app/src/main/java/com/android/apksig/internal/apk/stamp/V1SourceStampVerifier.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.apksig.internal.apk.stamp; - -import static com.android.apksig.internal.apk.ApkSigningBlockUtils.encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes; -import static com.android.apksig.internal.apk.stamp.SourceStampConstants.V1_SOURCE_STAMP_BLOCK_ID; - -import com.android.apksig.ApkVerifier; -import com.android.apksig.apk.ApkFormatException; -import com.android.apksig.apk.ApkUtils; -import com.android.apksig.internal.apk.ApkSigningBlockUtils; -import com.android.apksig.internal.apk.ContentDigestAlgorithm; -import com.android.apksig.internal.apk.SignatureInfo; -import com.android.apksig.internal.util.Pair; -import com.android.apksig.util.DataSource; - -import java.io.IOException; -import java.nio.BufferUnderflowException; -import java.nio.ByteBuffer; -import java.security.NoSuchAlgorithmException; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Map; - -/** - * Source Stamp verifier. - * - *

V1 of the source stamp verifies the stamp signature of at most one signature scheme. - */ -public abstract class V1SourceStampVerifier { - - /** Hidden constructor to prevent instantiation. */ - private V1SourceStampVerifier() {} - - /** - * Verifies the provided APK's SourceStamp signatures and returns the result of verification. - * The APK must be considered verified only if {@link ApkSigningBlockUtils.Result#verified} is - * {@code true}. If verification fails, the result will contain errors -- see {@link - * ApkSigningBlockUtils.Result#getErrors()}. - * - * @throws NoSuchAlgorithmException if the APK's signatures cannot be verified because a - * required cryptographic algorithm implementation is missing - * @throws ApkSigningBlockUtils.SignatureNotFoundException if no SourceStamp signatures are - * found - * @throws IOException if an I/O error occurs when reading the APK - */ - public static ApkSigningBlockUtils.Result verify( - DataSource apk, - ApkUtils.ZipSections zipSections, - byte[] sourceStampCertificateDigest, - Map apkContentDigests, - int minSdkVersion, - int maxSdkVersion) - throws IOException, NoSuchAlgorithmException, - ApkSigningBlockUtils.SignatureNotFoundException { - ApkSigningBlockUtils.Result result = - new ApkSigningBlockUtils.Result(ApkSigningBlockUtils.VERSION_SOURCE_STAMP); - SignatureInfo signatureInfo = - ApkSigningBlockUtils.findSignature( - apk, zipSections, V1_SOURCE_STAMP_BLOCK_ID, result); - - verify( - signatureInfo.signatureBlock, - sourceStampCertificateDigest, - apkContentDigests, - minSdkVersion, - maxSdkVersion, - result); - return result; - } - - /** - * Verifies the provided APK's SourceStamp 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}. See {@link #verify(DataSource, ApkUtils.ZipSections, byte[], Map, int, int)} for - * more information about the contract of this method. - */ - private static void verify( - ByteBuffer sourceStampBlock, - byte[] sourceStampCertificateDigest, - Map apkContentDigests, - int minSdkVersion, - int maxSdkVersion, - ApkSigningBlockUtils.Result result) - throws NoSuchAlgorithmException { - ApkSigningBlockUtils.Result.SignerInfo signerInfo = - new ApkSigningBlockUtils.Result.SignerInfo(); - result.signers.add(signerInfo); - try { - CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); - ByteBuffer sourceStampBlockData = - ApkSigningBlockUtils.getLengthPrefixedSlice(sourceStampBlock); - byte[] digestBytes = - encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes( - getApkDigests(apkContentDigests)); - SourceStampVerifier.verifyV1SourceStamp( - sourceStampBlockData, - certFactory, - signerInfo, - digestBytes, - sourceStampCertificateDigest, - minSdkVersion, - maxSdkVersion); - result.verified = !result.containsErrors() && !result.containsWarnings(); - } catch (CertificateException e) { - throw new IllegalStateException("Failed to obtain X.509 CertificateFactory", e); - } catch (ApkFormatException | BufferUnderflowException e) { - signerInfo.addWarning(ApkVerifier.Issue.SOURCE_STAMP_MALFORMED_SIGNATURE); - } - } - - private static List> getApkDigests( - Map apkContentDigests) { - List> digests = new ArrayList<>(); - for (Map.Entry apkContentDigest : - apkContentDigests.entrySet()) { - digests.add(Pair.of(apkContentDigest.getKey().getId(), apkContentDigest.getValue())); - } - Collections.sort(digests, Comparator.comparing(Pair::getFirst)); - return digests; - } -} diff --git a/app/src/main/java/com/android/apksig/internal/apk/stamp/V2SourceStampSigner.java b/app/src/main/java/com/android/apksig/internal/apk/stamp/V2SourceStampSigner.java deleted file mode 100644 index 1c1570aca5..0000000000 --- a/app/src/main/java/com/android/apksig/internal/apk/stamp/V2SourceStampSigner.java +++ /dev/null @@ -1,219 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.apk.stamp; - -import static com.android.apksig.internal.apk.ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2; -import static com.android.apksig.internal.apk.ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3; -import static com.android.apksig.internal.apk.ApkSigningBlockUtils.VERSION_JAR_SIGNATURE_SCHEME; -import static com.android.apksig.internal.apk.ApkSigningBlockUtils.encodeAsLengthPrefixedElement; -import static com.android.apksig.internal.apk.ApkSigningBlockUtils.encodeAsSequenceOfLengthPrefixedElements; -import static com.android.apksig.internal.apk.ApkSigningBlockUtils.encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes; - -import com.android.apksig.SigningCertificateLineage; -import com.android.apksig.internal.apk.ApkSigningBlockUtils; -import com.android.apksig.internal.apk.ApkSigningBlockUtils.SignerConfig; -import com.android.apksig.internal.apk.ContentDigestAlgorithm; -import com.android.apksig.internal.util.Pair; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.SignatureException; -import java.security.cert.CertificateEncodingException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * SourceStamp signer. - * - *

SourceStamp improves traceability of apps with respect to unauthorized distribution. - * - *

The stamp is part of the APK that is protected by the signing block. - * - *

The APK contents hash is signed using the stamp key, and is saved as part of the signing - * block. - * - *

V2 of the source stamp allows signing the digests of more than one signature schemes. - */ -public abstract class V2SourceStampSigner { - public static final int V2_SOURCE_STAMP_BLOCK_ID = - SourceStampConstants.V2_SOURCE_STAMP_BLOCK_ID; - - /** Hidden constructor to prevent instantiation. */ - private V2SourceStampSigner() { - } - - public static Pair generateSourceStampBlock( - SignerConfig sourceStampSignerConfig, - Map> signatureSchemeDigestInfos) - throws SignatureException, NoSuchAlgorithmException, InvalidKeyException { - if (sourceStampSignerConfig.certificates.isEmpty()) { - throw new SignatureException("No certificates configured for signer"); - } - - // Extract the digests for signature schemes. - List> signatureSchemeDigests = new ArrayList<>(); - getSignedDigestsFor( - VERSION_APK_SIGNATURE_SCHEME_V3, - signatureSchemeDigestInfos, - sourceStampSignerConfig, - signatureSchemeDigests); - getSignedDigestsFor( - VERSION_APK_SIGNATURE_SCHEME_V2, - signatureSchemeDigestInfos, - sourceStampSignerConfig, - signatureSchemeDigests); - getSignedDigestsFor( - VERSION_JAR_SIGNATURE_SCHEME, - signatureSchemeDigestInfos, - sourceStampSignerConfig, - signatureSchemeDigests); - Collections.sort(signatureSchemeDigests, Comparator.comparing(Pair::getFirst)); - - SourceStampBlock sourceStampBlock = new SourceStampBlock(); - - try { - sourceStampBlock.stampCertificate = - sourceStampSignerConfig.certificates.get(0).getEncoded(); - } catch (CertificateEncodingException e) { - throw new SignatureException( - "Retrieving the encoded form of the stamp certificate failed", e); - } - - sourceStampBlock.signedDigests = signatureSchemeDigests; - - sourceStampBlock.stampAttributes = encodeStampAttributes( - generateStampAttributes(sourceStampSignerConfig.mSigningCertificateLineage)); - sourceStampBlock.signedStampAttributes = - ApkSigningBlockUtils.generateSignaturesOverData(sourceStampSignerConfig, - sourceStampBlock.stampAttributes); - - // FORMAT: - // * length-prefixed bytes: X.509 certificate (ASN.1 DER encoded) - // * length-prefixed sequence of length-prefixed signed signature scheme digests: - // * uint32: signature scheme id - // * length-prefixed bytes: signed digests for the respective signature scheme - // * length-prefixed bytes: encoded stamp attributes - // * length-prefixed sequence of length-prefixed signed stamp attributes: - // * uint32: signature algorithm id - // * length-prefixed bytes: signed stamp attributes for the respective signature algorithm - byte[] sourceStampSignerBlock = - encodeAsSequenceOfLengthPrefixedElements( - new byte[][]{ - sourceStampBlock.stampCertificate, - encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes( - sourceStampBlock.signedDigests), - sourceStampBlock.stampAttributes, - encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes( - sourceStampBlock.signedStampAttributes), - }); - - // FORMAT: - // * length-prefixed stamp block. - return Pair.of(encodeAsLengthPrefixedElement(sourceStampSignerBlock), - SourceStampConstants.V2_SOURCE_STAMP_BLOCK_ID); - } - - private static void getSignedDigestsFor( - int signatureSchemeVersion, - Map> signatureSchemeDigestInfos, - SignerConfig sourceStampSignerConfig, - List> signatureSchemeDigests) - throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { - if (!signatureSchemeDigestInfos.containsKey(signatureSchemeVersion)) { - return; - } - - Map digestInfo = - signatureSchemeDigestInfos.get(signatureSchemeVersion); - List> digests = new ArrayList<>(); - for (Map.Entry digest : digestInfo.entrySet()) { - digests.add(Pair.of(digest.getKey().getId(), digest.getValue())); - } - Collections.sort(digests, Comparator.comparing(Pair::getFirst)); - - // FORMAT: - // * length-prefixed sequence of length-prefixed digests: - // * uint32: digest algorithm id - // * length-prefixed bytes: digest of the respective digest algorithm - byte[] digestBytes = - encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes(digests); - - // FORMAT: - // * length-prefixed sequence of length-prefixed signed digests: - // * uint32: signature algorithm id - // * length-prefixed bytes: signed digest for the respective signature algorithm - List> signedDigest = - ApkSigningBlockUtils.generateSignaturesOverData( - sourceStampSignerConfig, digestBytes); - - // FORMAT: - // * length-prefixed sequence of length-prefixed signed signature scheme digests: - // * uint32: signature scheme id - // * length-prefixed bytes: signed digests for the respective signature scheme - signatureSchemeDigests.add( - Pair.of( - signatureSchemeVersion, - encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes( - signedDigest))); - } - - private static byte[] encodeStampAttributes(Map stampAttributes) { - int payloadSize = 0; - for (byte[] attributeValue : stampAttributes.values()) { - // Pair size + Attribute ID + Attribute value - payloadSize += 4 + 4 + attributeValue.length; - } - - // FORMAT (little endian): - // * length-prefixed bytes: pair - // * uint32: ID - // * bytes: value - ByteBuffer result = ByteBuffer.allocate(4 + payloadSize); - result.order(ByteOrder.LITTLE_ENDIAN); - result.putInt(payloadSize); - for (Map.Entry stampAttribute : stampAttributes.entrySet()) { - // Pair size - result.putInt(4 + stampAttribute.getValue().length); - result.putInt(stampAttribute.getKey()); - result.put(stampAttribute.getValue()); - } - return result.array(); - } - - private static Map generateStampAttributes(SigningCertificateLineage lineage) { - HashMap stampAttributes = new HashMap<>(); - if (lineage != null) { - stampAttributes.put(SourceStampConstants.PROOF_OF_ROTATION_ATTR_ID, - lineage.encodeSigningCertificateLineage()); - } - return stampAttributes; - } - - private static final class SourceStampBlock { - public byte[] stampCertificate; - public List> signedDigests; - // Optional stamp attributes that are not required for verification. - public byte[] stampAttributes; - public List> signedStampAttributes; - } -} diff --git a/app/src/main/java/com/android/apksig/internal/apk/stamp/V2SourceStampVerifier.java b/app/src/main/java/com/android/apksig/internal/apk/stamp/V2SourceStampVerifier.java deleted file mode 100644 index 5ba3618c8e..0000000000 --- a/app/src/main/java/com/android/apksig/internal/apk/stamp/V2SourceStampVerifier.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.apk.stamp; - -import static com.android.apksig.internal.apk.ApkSigningBlockUtilsLite.encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes; -import static com.android.apksig.internal.apk.stamp.SourceStampConstants.V2_SOURCE_STAMP_BLOCK_ID; - -import com.android.apksig.ApkVerificationIssue; -import com.android.apksig.Constants; -import com.android.apksig.apk.ApkFormatException; -import com.android.apksig.internal.apk.ApkSigResult; -import com.android.apksig.internal.apk.ApkSignerInfo; -import com.android.apksig.internal.apk.ApkSigningBlockUtilsLite; -import com.android.apksig.internal.apk.ContentDigestAlgorithm; -import com.android.apksig.internal.apk.SignatureInfo; -import com.android.apksig.internal.apk.SignatureNotFoundException; -import com.android.apksig.internal.util.Pair; -import com.android.apksig.util.DataSource; -import com.android.apksig.zip.ZipSections; - -import java.io.IOException; -import java.nio.BufferUnderflowException; -import java.nio.ByteBuffer; -import java.security.NoSuchAlgorithmException; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Source Stamp verifier. - * - *

V2 of the source stamp verifies the stamp signature of more than one signature schemes. - */ -public abstract class V2SourceStampVerifier { - - /** Hidden constructor to prevent instantiation. */ - private V2SourceStampVerifier() {} - - /** - * Verifies the provided APK's SourceStamp signatures and returns the result of verification. - * The APK must be considered verified only if {@link ApkSigResult#verified} is - * {@code true}. If verification fails, the result will contain errors -- see {@link - * ApkSigResult#getErrors()}. - * - * @throws NoSuchAlgorithmException if the APK's signatures cannot be verified because a - * required cryptographic algorithm implementation is missing - * @throws SignatureNotFoundException if no SourceStamp signatures are - * found - * @throws IOException if an I/O error occurs when reading the APK - */ - public static ApkSigResult verify( - DataSource apk, - ZipSections zipSections, - byte[] sourceStampCertificateDigest, - Map> signatureSchemeApkContentDigests, - int minSdkVersion, - int maxSdkVersion) - throws IOException, NoSuchAlgorithmException, SignatureNotFoundException { - ApkSigResult result = - new ApkSigResult(Constants.VERSION_SOURCE_STAMP); - SignatureInfo signatureInfo = - ApkSigningBlockUtilsLite.findSignature( - apk, zipSections, V2_SOURCE_STAMP_BLOCK_ID); - - verify( - signatureInfo.signatureBlock, - sourceStampCertificateDigest, - signatureSchemeApkContentDigests, - minSdkVersion, - maxSdkVersion, - result); - return result; - } - - /** - * Verifies the provided APK's SourceStamp 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}. See {@link #verify(DataSource, ZipSections, byte[], Map, int, int)} for - * more information about the contract of this method. - */ - private static void verify( - ByteBuffer sourceStampBlock, - byte[] sourceStampCertificateDigest, - Map> signatureSchemeApkContentDigests, - int minSdkVersion, - int maxSdkVersion, - ApkSigResult result) - throws NoSuchAlgorithmException { - ApkSignerInfo signerInfo = new ApkSignerInfo(); - result.mSigners.add(signerInfo); - try { - CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); - ByteBuffer sourceStampBlockData = - ApkSigningBlockUtilsLite.getLengthPrefixedSlice(sourceStampBlock); - SourceStampVerifier.verifyV2SourceStamp( - sourceStampBlockData, - certFactory, - signerInfo, - getSignatureSchemeDigests(signatureSchemeApkContentDigests), - sourceStampCertificateDigest, - minSdkVersion, - maxSdkVersion); - result.verified = !result.containsErrors() && !result.containsWarnings(); - } catch (CertificateException e) { - throw new IllegalStateException("Failed to obtain X.509 CertificateFactory", e); - } catch (ApkFormatException | BufferUnderflowException e) { - signerInfo.addWarning(ApkVerificationIssue.SOURCE_STAMP_MALFORMED_SIGNATURE); - } - } - - private static Map getSignatureSchemeDigests( - Map> signatureSchemeApkContentDigests) { - Map digests = new HashMap<>(); - for (Map.Entry> - signatureSchemeApkContentDigest : signatureSchemeApkContentDigests.entrySet()) { - List> apkDigests = - getApkDigests(signatureSchemeApkContentDigest.getValue()); - digests.put( - signatureSchemeApkContentDigest.getKey(), - encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes(apkDigests)); - } - return digests; - } - - private static List> getApkDigests( - Map apkContentDigests) { - List> digests = new ArrayList<>(); - for (Map.Entry apkContentDigest : - apkContentDigests.entrySet()) { - digests.add(Pair.of(apkContentDigest.getKey().getId(), apkContentDigest.getValue())); - } - Collections.sort(digests, Comparator.comparing(Pair::getFirst)); - return digests; - } -} diff --git a/app/src/main/java/com/android/apksig/internal/apk/v1/DigestAlgorithm.java b/app/src/main/java/com/android/apksig/internal/apk/v1/DigestAlgorithm.java deleted file mode 100644 index 51b9810fa9..0000000000 --- a/app/src/main/java/com/android/apksig/internal/apk/v1/DigestAlgorithm.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.apk.v1; - -import java.util.Comparator; - -/** - * 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; - } - - public static Comparator BY_STRENGTH_COMPARATOR = new StrengthComparator(); - - private static class StrengthComparator implements Comparator { - @Override - public int compare(DigestAlgorithm a1, DigestAlgorithm a2) { - switch (a1) { - case SHA1: - switch (a2) { - case SHA1: - return 0; - case SHA256: - return -1; - } - throw new RuntimeException("Unsupported algorithm: " + a2); - - case SHA256: - switch (a2) { - case SHA1: - return 1; - case SHA256: - return 0; - } - throw new RuntimeException("Unsupported algorithm: " + a2); - - default: - throw new RuntimeException("Unsupported algorithm: " + a1); - } - } - } -} diff --git a/app/src/main/java/com/android/apksig/internal/apk/v1/V1SchemeConstants.java b/app/src/main/java/com/android/apksig/internal/apk/v1/V1SchemeConstants.java deleted file mode 100644 index db1d15f618..0000000000 --- a/app/src/main/java/com/android/apksig/internal/apk/v1/V1SchemeConstants.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.apk.v1; - -/** Constants used by the Jar Signing / V1 Signature Scheme signing and verification. */ -public class V1SchemeConstants { - private V1SchemeConstants() {} - - public static final String MANIFEST_ENTRY_NAME = "META-INF/MANIFEST.MF"; - public static final String SF_ATTRIBUTE_NAME_ANDROID_APK_SIGNED_NAME_STR = - "X-Android-APK-Signed"; -} diff --git a/app/src/main/java/com/android/apksig/internal/apk/v1/V1SchemeSigner.java b/app/src/main/java/com/android/apksig/internal/apk/v1/V1SchemeSigner.java deleted file mode 100644 index ee3c9d860b..0000000000 --- a/app/src/main/java/com/android/apksig/internal/apk/v1/V1SchemeSigner.java +++ /dev/null @@ -1,580 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.apk.v1; - -import static com.android.apksig.Constants.OID_RSA_ENCRYPTION; -import static com.android.apksig.internal.pkcs7.AlgorithmIdentifier.getSignerInfoDigestAlgorithmOid; -import static com.android.apksig.internal.pkcs7.AlgorithmIdentifier.getSignerInfoSignatureAlgorithm; - -import com.android.apksig.apk.ApkFormatException; -import com.android.apksig.internal.apk.ApkSigningBlockUtils; -import com.android.apksig.internal.asn1.Asn1EncodingException; -import com.android.apksig.internal.jar.ManifestWriter; -import com.android.apksig.internal.jar.SignatureFileWriter; -import com.android.apksig.internal.pkcs7.AlgorithmIdentifier; -import com.android.apksig.internal.util.Pair; - -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.CertificateEncodingException; -import java.security.cert.CertificateException; -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; - -/** - * APK signer which uses JAR signing (aka v1 signing scheme). - * - * @see Signed JAR File - */ -public abstract class V1SchemeSigner { - public static final String MANIFEST_ENTRY_NAME = V1SchemeConstants.MANIFEST_ENTRY_NAME; - - private static final Attributes.Name ATTRIBUTE_NAME_CREATED_BY = - new Attributes.Name("Created-By"); - private static final String ATTRIBUTE_VALUE_MANIFEST_VERSION = "1.0"; - private static final String ATTRIBUTE_VALUE_SIGNATURE_VERSION = "1.0"; - - private static final Attributes.Name SF_ATTRIBUTE_NAME_ANDROID_APK_SIGNED_NAME = - new Attributes.Name(V1SchemeConstants.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 certificates; - - /** - * Digest algorithm used for the signature. - */ - public DigestAlgorithm signatureDigestAlgorithm; - - /** - * If DSA is the signing algorithm, whether or not deterministic DSA signing should be used. - */ - public boolean deterministicDsaSigning; - } - - /** 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) || OID_RSA_ENCRYPTION.equals((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"); - } - return DigestAlgorithm.SHA256; - } else { - throw new InvalidKeyException("Unsupported key algorithm: " + keyAlgorithm); - } - } - - /** - * Returns a safe version of the provided signer name. - */ - public static String getSafeSignerName(String name) { - if (name.isEmpty()) { - throw new IllegalArgumentException("Empty name"); - } - - // According to https://docs.oracle.com/javase/tutorial/deployment/jar/signing.html, the - // name must not be longer than 8 characters and may contain only A-Z, 0-9, _, and -. - StringBuilder result = new StringBuilder(); - char[] nameCharsUpperCase = name.toUpperCase(Locale.US).toCharArray(); - for (int i = 0; i < Math.min(nameCharsUpperCase.length, 8); i++) { - char c = nameCharsUpperCase[i]; - if (((c >= 'A') && (c <= 'Z')) - || ((c >= '0') && (c <= '9')) - || (c == '-') - || (c == '_')) { - result.append(c); - } else { - result.append('_'); - } - } - return result.toString(); - } - - /** - * 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 which represent directories sould not be listed in the manifest. - if (entryName.endsWith("/")) { - return false; - } - - // 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 ApkFormatException if the source manifest is malformed - * @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> sign( - List signerConfigs, - DigestAlgorithm jarEntryDigestAlgorithm, - Map jarEntryDigests, - List apkSigningSchemeIds, - byte[] sourceManifestBytes, - String createdBy) - throws NoSuchAlgorithmException, ApkFormatException, 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, createdBy, 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> signManifest( - List signerConfigs, - DigestAlgorithm digestAlgorithm, - List apkSigningSchemeIds, - String createdBy, - 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> signatureJarEntries = - new ArrayList<>(2 * signerConfigs.size() + 1); - byte[] sfBytes = - generateSignatureFile(apkSigningSchemeIds, digestAlgorithm, createdBy, 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(V1SchemeConstants.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 getOutputEntryNames(List signerConfigs) { - Set 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(V1SchemeConstants.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 jarEntryDigests, - byte[] sourceManifestBytes) throws ApkFormatException { - Manifest sourceManifest = null; - if (sourceManifestBytes != null) { - try { - sourceManifest = new Manifest(new ByteArrayInputStream(sourceManifestBytes)); - } catch (IOException e) { - throw new ApkFormatException("Malformed source META-INF/MANIFEST.MF", e); - } - } - ByteArrayOutputStream manifestOut = new ByteArrayOutputStream(); - Attributes mainAttrs = new Attributes(); - // Copy the main section from the source manifest (if provided). Otherwise use defaults. - // NOTE: We don't output our own Created-By header because this signer did not create the - // JAR/APK being signed -- the signer only adds signatures to the already existing - // JAR/APK. - if (sourceManifest != null) { - mainAttrs.putAll(sourceManifest.getMainAttributes()); - } else { - mainAttrs.put(Attributes.Name.MANIFEST_VERSION, ATTRIBUTE_VALUE_MANIFEST_VERSION); - } - - try { - ManifestWriter.writeMainSection(manifestOut, mainAttrs); - } catch (IOException e) { - throw new RuntimeException("Failed to write in-memory MANIFEST.MF", e); - } - - List sortedEntryNames = new ArrayList<>(jarEntryDigests.keySet()); - Collections.sort(sortedEntryNames); - SortedMap invidualSectionsContents = new TreeMap<>(); - String entryDigestAttributeName = getEntryDigestAttributeName(jarEntryDigestAlgorithm); - for (String entryName : sortedEntryNames) { - checkEntryNameValid(entryName); - 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; - } - - private static void checkEntryNameValid(String name) throws ApkFormatException { - // JAR signing spec says CR, LF, and NUL are not permitted in entry names - // CR or LF in entry names will result in malformed MANIFEST.MF and .SF files because there - // is no way to escape characters in MANIFEST.MF and .SF files. NUL can, presumably, cause - // issues when parsing using C and C++ like languages. - for (char c : name.toCharArray()) { - if ((c == '\r') || (c == '\n') || (c == 0)) { - throw new ApkFormatException( - String.format( - "Unsupported character 0x%1$02x in ZIP entry name \"%2$s\"", - (int) c, - name)); - } - } - } - - public static class OutputManifestFile { - public byte[] contents; - public SortedMap individualSectionsContents; - public Attributes mainSectionAttributes; - } - - private static byte[] generateSignatureFile( - List apkSignatureSchemeIds, - DigestAlgorithm manifestDigestAlgorithm, - String createdBy, - 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, createdBy); - 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 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(); - } - - - - /** - * Generates the CMS PKCS #7 signature block corresponding to the provided signature file and - * signing configuration. - */ - private static byte[] generateSignatureBlock( - SignerConfig signerConfig, byte[] signatureFileBytes) - throws NoSuchAlgorithmException, InvalidKeyException, CertificateException, - SignatureException { - // Obtain relevant bits of signing configuration - List signerCerts = signerConfig.certificates; - X509Certificate signingCert = signerCerts.get(0); - PublicKey publicKey = signingCert.getPublicKey(); - DigestAlgorithm digestAlgorithm = signerConfig.signatureDigestAlgorithm; - Pair signatureAlgs = - getSignerInfoSignatureAlgorithm(publicKey, digestAlgorithm, - signerConfig.deterministicDsaSigning); - String jcaSignatureAlgorithm = signatureAlgs.getFirst(); - - // Generate the cryptographic signature of the signature file - byte[] signatureBytes; - try { - Signature signature = Signature.getInstance(jcaSignatureAlgorithm); - signature.initSign(signerConfig.privateKey); - signature.update(signatureFileBytes); - signatureBytes = signature.sign(); - } catch (InvalidKeyException e) { - throw new InvalidKeyException("Failed to sign using " + jcaSignatureAlgorithm, e); - } catch (SignatureException e) { - throw new SignatureException("Failed to sign using " + jcaSignatureAlgorithm, e); - } - - // Verify the signature against the public key in the signing certificate - try { - Signature signature = Signature.getInstance(jcaSignatureAlgorithm); - signature.initVerify(publicKey); - signature.update(signatureFileBytes); - 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 (SignatureException e) { - throw new SignatureException( - "Failed to verify generated " + jcaSignatureAlgorithm + " signature using" - + " public key from certificate", - e); - } - - AlgorithmIdentifier digestAlgorithmId = - getSignerInfoDigestAlgorithmOid(digestAlgorithm); - AlgorithmIdentifier signatureAlgorithmId = signatureAlgs.getSecond(); - try { - return ApkSigningBlockUtils.generatePkcs7DerEncodedMessage( - signatureBytes, - null, - signerCerts, digestAlgorithmId, - signatureAlgorithmId); - } catch (Asn1EncodingException | CertificateEncodingException ex) { - throw new SignatureException("Failed to encode signature block"); - } - } - - - - 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/app/src/main/java/com/android/apksig/internal/apk/v1/V1SchemeVerifier.java b/app/src/main/java/com/android/apksig/internal/apk/v1/V1SchemeVerifier.java deleted file mode 100644 index 0ebef0e92f..0000000000 --- a/app/src/main/java/com/android/apksig/internal/apk/v1/V1SchemeVerifier.java +++ /dev/null @@ -1,1564 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.apk.v1; - -import static com.android.apksig.internal.oid.OidConstants.getSigAlgSupportedApiLevels; -import static com.android.apksig.internal.pkcs7.AlgorithmIdentifier.getJcaDigestAlgorithm; -import static com.android.apksig.internal.pkcs7.AlgorithmIdentifier.getJcaSignatureAlgorithm; -import static com.android.apksig.internal.x509.Certificate.findCertificate; -import static com.android.apksig.internal.x509.Certificate.parseCertificates; - -import com.android.apksig.ApkVerifier.Issue; -import com.android.apksig.ApkVerifier.IssueWithParams; -import com.android.apksig.apk.ApkFormatException; -import com.android.apksig.apk.ApkUtils; -import com.android.apksig.internal.apk.ApkSigningBlockUtils; -import com.android.apksig.internal.asn1.Asn1BerParser; -import com.android.apksig.internal.asn1.Asn1Class; -import com.android.apksig.internal.asn1.Asn1DecodingException; -import com.android.apksig.internal.asn1.Asn1Field; -import com.android.apksig.internal.asn1.Asn1OpaqueObject; -import com.android.apksig.internal.asn1.Asn1Type; -import com.android.apksig.internal.jar.ManifestParser; -import com.android.apksig.internal.oid.OidConstants; -import com.android.apksig.internal.pkcs7.Attribute; -import com.android.apksig.internal.pkcs7.ContentInfo; -import com.android.apksig.internal.pkcs7.Pkcs7Constants; -import com.android.apksig.internal.pkcs7.Pkcs7DecodingException; -import com.android.apksig.internal.pkcs7.SignedData; -import com.android.apksig.internal.pkcs7.SignerInfo; -import com.android.apksig.internal.util.AndroidSdkVersion; -import com.android.apksig.internal.util.ByteBufferUtils; -import com.android.apksig.internal.util.InclusiveIntRange; -import com.android.apksig.internal.util.Pair; -import com.android.apksig.internal.zip.CentralDirectoryRecord; -import com.android.apksig.internal.zip.LocalFileRecord; -import com.android.apksig.internal.zip.ZipUtils; -import com.android.apksig.util.DataSinks; -import com.android.apksig.util.DataSource; -import com.android.apksig.zip.ZipFormatException; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.security.InvalidKeyException; -import java.security.KeyFactory; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.Principal; -import java.security.PublicKey; -import java.security.Signature; -import java.security.SignatureException; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.X509EncodedKeySpec; -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; - -/** - * APK verifier which uses JAR signing (aka v1 signing scheme). - * - * @see Signed JAR File - */ -public abstract class V1SchemeVerifier { - 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()}. - * - *

Verification succeeds iff the APK's JAR signatures are expected to verify on all Android - * platform versions in the {@code [minSdkVersion, maxSdkVersion]} range. If the APK's signature - * is expected to not verify on any of the specified platform versions, this method returns a - * result with one or more errors and whose {@code Result.verified == false}, or this method - * throws an exception. - * - * @throws ApkFormatException 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 supportedApkSigSchemeNames, - Set foundApkSigSchemeIds, - int minSdkVersion, - int maxSdkVersion) throws IOException, ApkFormatException, 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 cdRecords = parseZipCentralDirectory(apk, apkSections); - Set 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 checkForDuplicateEntries( - List cdRecords, Result result) { - Set cdEntryNames = new HashSet<>(cdRecords.size()); - Set 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; - } - - /** - * Parses raw representation of MANIFEST.MF file into a pair of main entry manifest section - * representation and a mapping between entry name and its manifest section representation. - * - * @param manifestBytes raw representation of Manifest.MF - * @param cdEntryNames expected set of entry names - * @param result object to keep track of errors that happened during the parsing - * @return a pair of main entry manifest section representation and a mapping between entry name - * and its manifest section representation - */ - public static Pair> parseManifest( - byte[] manifestBytes, Set cdEntryNames, Result result) { - ManifestParser manifest = new ManifestParser(manifestBytes); - ManifestParser.Section manifestMainSection = manifest.readSection(); - List manifestIndividualSections = manifest.readAllSections(); - Map 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; - } - } - return Pair.of(manifestMainSection, entryNameToManifestSection); - } - - /** - * 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 cdRecords, - Set cdEntryNames, - Map supportedApkSigSchemeNames, - Set foundApkSigSchemeIds, - int minSdkVersion, - int maxSdkVersion, - Result result) throws ApkFormatException, IOException, NoSuchAlgorithmException { - - // Find JAR manifest and signature block files. - CentralDirectoryRecord manifestEntry = null; - Map sigFileEntries = new HashMap<>(1); - List sigBlockEntries = new ArrayList<>(1); - for (CentralDirectoryRecord cdRecord : cdRecords) { - String entryName = cdRecord.getName(); - if (!entryName.startsWith("META-INF/")) { - continue; - } - if ((manifestEntry == null) && (V1SchemeConstants.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; - try { - manifestBytes = - LocalFileRecord.getUncompressedData(apk, manifestEntry, cdStartOffset); - } catch (ZipFormatException e) { - throw new ApkFormatException("Malformed ZIP entry: " + manifestEntry.getName(), e); - } - - Pair> manifestSections = - parseManifest(manifestBytes, cdEntryNames, result); - - if (result.containsErrors()) { - return; - } - - ManifestParser.Section manifestMainSection = manifestSections.getFirst(); - Map entryNameToManifestSection = - manifestSections.getSecond(); - - // STATE OF AFFAIRS: - // * All JAR entries listed in JAR manifest are present in the APK. - - // Identify signers - List 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 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 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 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; - } - } - - 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 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 getSigFileEntryNames() { - return mSigFileEntryNames; - } - - public Result.SignerInfo getResult() { - return mResult; - } - - public void verifySigBlockAgainstSigFile( - DataSource apk, long cdStartOffset, int minSdkVersion, int maxSdkVersion) - throws IOException, ApkFormatException, NoSuchAlgorithmException { - // Obtain the signature block from the APK - byte[] sigBlockBytes; - try { - sigBlockBytes = - LocalFileRecord.getUncompressedData( - apk, mSignatureBlockEntry, cdStartOffset); - } catch (ZipFormatException e) { - throw new ApkFormatException( - "Malformed ZIP entry: " + mSignatureBlockEntry.getName(), e); - } - // Obtain the signature file from the APK - try { - mSigFileBytes = - LocalFileRecord.getUncompressedData( - apk, mSignatureFileEntry, cdStartOffset); - } catch (ZipFormatException e) { - throw new ApkFormatException( - "Malformed ZIP entry: " + mSignatureFileEntry.getName(), e); - } - - // Extract PKCS #7 SignedData from the signature block - SignedData signedData; - try { - ContentInfo contentInfo = - Asn1BerParser.parse(ByteBuffer.wrap(sigBlockBytes), ContentInfo.class); - if (!Pkcs7Constants.OID_SIGNED_DATA.equals(contentInfo.contentType)) { - throw new Asn1DecodingException( - "Unsupported ContentInfo.contentType: " + contentInfo.contentType); - } - signedData = - Asn1BerParser.parse(contentInfo.content.getEncoded(), SignedData.class); - } catch (Asn1DecodingException e) { - e.printStackTrace(); - mResult.addError( - Issue.JAR_SIG_PARSE_EXCEPTION, mSignatureBlockEntry.getName(), e); - return; - } - - if (signedData.signerInfos.isEmpty()) { - mResult.addError(Issue.JAR_SIG_NO_SIGNERS, mSignatureBlockEntry.getName()); - return; - } - - // Find the first SignedData.SignerInfos element which verifies against the signature - // file - SignerInfo firstVerifiedSignerInfo = null; - X509Certificate firstVerifiedSignerInfoSigningCertificate = null; - // Prior to Android N, Android attempts to verify only the first SignerInfo. From N - // onwards, Android attempts to verify all SignerInfos and then picks the first verified - // SignerInfo. - List unverifiedSignerInfosToTry; - if (minSdkVersion < AndroidSdkVersion.N) { - unverifiedSignerInfosToTry = - Collections.singletonList(signedData.signerInfos.get(0)); - } else { - unverifiedSignerInfosToTry = signedData.signerInfos; - } - List signedDataCertificates = null; - for (SignerInfo unverifiedSignerInfo : unverifiedSignerInfosToTry) { - // Parse SignedData.certificates -- they are needed to verify SignerInfo - if (signedDataCertificates == null) { - try { - signedDataCertificates = parseCertificates(signedData.certificates); - } catch (CertificateException e) { - mResult.addError( - Issue.JAR_SIG_PARSE_EXCEPTION, mSignatureBlockEntry.getName(), e); - return; - } - } - - // Verify SignerInfo - X509Certificate signingCertificate; - try { - signingCertificate = - verifySignerInfoAgainstSigFile( - signedData, - signedDataCertificates, - unverifiedSignerInfo, - mSigFileBytes, - minSdkVersion, - maxSdkVersion); - if (mResult.containsErrors()) { - return; - } - if (signingCertificate != null) { - // SignerInfo verified - if (firstVerifiedSignerInfo == null) { - firstVerifiedSignerInfo = unverifiedSignerInfo; - firstVerifiedSignerInfoSigningCertificate = signingCertificate; - } - } - } catch (Pkcs7DecodingException e) { - mResult.addError( - Issue.JAR_SIG_PARSE_EXCEPTION, mSignatureBlockEntry.getName(), e); - return; - } catch (InvalidKeyException | SignatureException e) { - mResult.addError( - Issue.JAR_SIG_VERIFY_EXCEPTION, - mSignatureBlockEntry.getName(), - mSignatureFileEntry.getName(), - e); - return; - } - } - if (firstVerifiedSignerInfo == null) { - // No SignerInfo verified - mResult.addError( - Issue.JAR_SIG_DID_NOT_VERIFY, - mSignatureBlockEntry.getName(), - mSignatureFileEntry.getName()); - return; - } - // Verified - List signingCertChain = - getCertificateChain( - signedDataCertificates, firstVerifiedSignerInfoSigningCertificate); - mResult.certChain.clear(); - mResult.certChain.addAll(signingCertChain); - } - - /** - * Returns the signing certificate if the provided {@link SignerInfo} verifies against the - * contents of the provided signature file, or {@code null} if it does not verify. - */ - private X509Certificate verifySignerInfoAgainstSigFile( - SignedData signedData, - Collection signedDataCertificates, - SignerInfo signerInfo, - byte[] signatureFile, - int minSdkVersion, - int maxSdkVersion) - throws Pkcs7DecodingException, NoSuchAlgorithmException, - InvalidKeyException, SignatureException { - String digestAlgorithmOid = signerInfo.digestAlgorithm.algorithm; - String signatureAlgorithmOid = signerInfo.signatureAlgorithm.algorithm; - InclusiveIntRange desiredApiLevels = - InclusiveIntRange.fromTo(minSdkVersion, maxSdkVersion); - List apiLevelsWhereDigestAndSigAlgorithmSupported = - getSigAlgSupportedApiLevels(digestAlgorithmOid, signatureAlgorithmOid); - List apiLevelsWhereDigestAlgorithmNotSupported = - desiredApiLevels.getValuesNotIn(apiLevelsWhereDigestAndSigAlgorithmSupported); - if (!apiLevelsWhereDigestAlgorithmNotSupported.isEmpty()) { - String digestAlgorithmUserFriendly = - OidConstants.OidToUserFriendlyNameMapper.getUserFriendlyNameForOid( - digestAlgorithmOid); - if (digestAlgorithmUserFriendly == null) { - digestAlgorithmUserFriendly = digestAlgorithmOid; - } - String signatureAlgorithmUserFriendly = - OidConstants.OidToUserFriendlyNameMapper.getUserFriendlyNameForOid( - signatureAlgorithmOid); - if (signatureAlgorithmUserFriendly == null) { - signatureAlgorithmUserFriendly = signatureAlgorithmOid; - } - StringBuilder apiLevelsUserFriendly = new StringBuilder(); - for (InclusiveIntRange range : apiLevelsWhereDigestAlgorithmNotSupported) { - if (apiLevelsUserFriendly.length() > 0) { - apiLevelsUserFriendly.append(", "); - } - if (range.getMin() == range.getMax()) { - apiLevelsUserFriendly.append(String.valueOf(range.getMin())); - } else if (range.getMax() == Integer.MAX_VALUE) { - apiLevelsUserFriendly.append(range.getMin() + "+"); - } else { - apiLevelsUserFriendly.append(range.getMin() + "-" + range.getMax()); - } - } - mResult.addError( - Issue.JAR_SIG_UNSUPPORTED_SIG_ALG, - mSignatureBlockEntry.getName(), - digestAlgorithmOid, - signatureAlgorithmOid, - apiLevelsUserFriendly.toString(), - digestAlgorithmUserFriendly, - signatureAlgorithmUserFriendly); - return null; - } - - // From the bag of certs, obtain the certificate referenced by the SignerInfo, - // and verify the cryptographic signature in the SignerInfo against the certificate. - - // Locate the signing certificate referenced by the SignerInfo - X509Certificate signingCertificate = - findCertificate(signedDataCertificates, signerInfo.sid); - if (signingCertificate == null) { - throw new SignatureException( - "Signing certificate referenced in SignerInfo not found in" - + " SignedData"); - } - - // Check whether the signing certificate is acceptable. Android performs these - // checks explicitly, instead of delegating this to - // Signature.initVerify(Certificate). - if (signingCertificate.hasUnsupportedCriticalExtension()) { - throw new SignatureException( - "Signing certificate has unsupported critical extensions"); - } - boolean[] keyUsageExtension = signingCertificate.getKeyUsage(); - if (keyUsageExtension != null) { - boolean digitalSignature = - (keyUsageExtension.length >= 1) && (keyUsageExtension[0]); - boolean nonRepudiation = - (keyUsageExtension.length >= 2) && (keyUsageExtension[1]); - if ((!digitalSignature) && (!nonRepudiation)) { - throw new SignatureException( - "Signing certificate not authorized for use in digital signatures" - + ": keyUsage extension missing digitalSignature and" - + " nonRepudiation"); - } - } - - // Verify the cryptographic signature in SignerInfo against the certificate's - // public key - String jcaSignatureAlgorithm = - getJcaSignatureAlgorithm(digestAlgorithmOid, signatureAlgorithmOid); - Signature s = Signature.getInstance(jcaSignatureAlgorithm); - PublicKey publicKey = signingCertificate.getPublicKey(); - try { - s.initVerify(publicKey); - } catch (InvalidKeyException e) { - // An InvalidKeyException could be caught if the PublicKey in the certificate is not - // properly encoded; attempt to resolve any encoding errors, generate a new public - // key, and reattempt the initVerify with the newly encoded key. - try { - byte[] encodedPublicKey = ApkSigningBlockUtils.encodePublicKey(publicKey); - publicKey = KeyFactory.getInstance(publicKey.getAlgorithm()).generatePublic( - new X509EncodedKeySpec(encodedPublicKey)); - } catch (InvalidKeySpecException ikse) { - // If an InvalidKeySpecException is caught then throw the original Exception - // since the key couldn't be properly re-encoded, and the original Exception - // will have more useful debugging info. - throw e; - } - s = Signature.getInstance(jcaSignatureAlgorithm); - s.initVerify(publicKey); - } - - if (signerInfo.signedAttrs != null) { - // Signed attributes present -- verify signature against the ASN.1 DER encoded form - // of signed attributes. This verifies integrity of the signature file because - // signed attributes must contain the digest of the signature file. - if (minSdkVersion < AndroidSdkVersion.KITKAT) { - // Prior to Android KitKat, APKs with signed attributes are unsafe: - // * The APK's contents are not protected by the JAR signature because the - // digest in signed attributes is not verified. This means an attacker can - // arbitrarily modify the APK without invalidating its signature. - // * Luckily, the signature over signed attributes was verified incorrectly - // (over the verbatim IMPLICIT [0] form rather than over re-encoded - // UNIVERSAL SET form) which means that JAR signatures which would verify on - // pre-KitKat Android and yet do not protect the APK from modification could - // be generated only by broken tools or on purpose by the entity signing the - // APK. - // - // We thus reject such unsafe APKs, even if they verify on platforms before - // KitKat. - throw new SignatureException( - "APKs with Signed Attributes broken on platforms with API Level < " - + AndroidSdkVersion.KITKAT); - } - try { - List signedAttributes = - Asn1BerParser.parseImplicitSetOf( - signerInfo.signedAttrs.getEncoded(), Attribute.class); - SignedAttributes signedAttrs = new SignedAttributes(signedAttributes); - if (maxSdkVersion >= AndroidSdkVersion.N) { - // Content Type attribute is checked only on Android N and newer - String contentType = - signedAttrs.getSingleObjectIdentifierValue( - Pkcs7Constants.OID_CONTENT_TYPE); - if (contentType == null) { - throw new SignatureException("No Content Type in signed attributes"); - } - if (!contentType.equals(signedData.encapContentInfo.contentType)) { - // Did not verify: Content type signed attribute does not match - // SignedData.encapContentInfo.eContentType. This fails verification of - // this SignerInfo but should not prevent verification of other - // SignerInfos. Hence, no exception is thrown. - return null; - } - } - byte[] expectedSignatureFileDigest = - signedAttrs.getSingleOctetStringValue( - Pkcs7Constants.OID_MESSAGE_DIGEST); - if (expectedSignatureFileDigest == null) { - throw new SignatureException("No content digest in signed attributes"); - } - byte[] actualSignatureFileDigest = - MessageDigest.getInstance( - getJcaDigestAlgorithm(digestAlgorithmOid)) - .digest(signatureFile); - if (!Arrays.equals( - expectedSignatureFileDigest, actualSignatureFileDigest)) { - // Skip verification: signature file digest in signed attributes does not - // match the signature file. This fails verification of - // this SignerInfo but should not prevent verification of other - // SignerInfos. Hence, no exception is thrown. - return null; - } - } catch (Asn1DecodingException e) { - throw new SignatureException("Failed to parse signed attributes", e); - } - // PKCS #7 requires that signature is over signed attributes re-encoded as - // ASN.1 DER. However, Android does not re-encode except for changing the - // first byte of encoded form from IMPLICIT [0] to UNIVERSAL SET. We do the - // same for maximum compatibility. - ByteBuffer signedAttrsOriginalEncoding = signerInfo.signedAttrs.getEncoded(); - s.update((byte) 0x31); // UNIVERSAL SET - signedAttrsOriginalEncoding.position(1); - s.update(signedAttrsOriginalEncoding); - } else { - // No signed attributes present -- verify signature against the contents of the - // signature file - s.update(signatureFile); - } - byte[] sigBytes = ByteBufferUtils.toByteArray(signerInfo.signature.slice()); - if (!s.verify(sigBytes)) { - // Cryptographic signature did not verify. This fails verification of this - // SignerInfo but should not prevent verification of other SignerInfos. Hence, no - // exception is thrown. - return null; - } - // Cryptographic signature verified - return signingCertificate; - } - - - - public static List getCertificateChain( - List certs, X509Certificate leaf) { - List unusedCerts = new ArrayList<>(certs); - List result = new ArrayList<>(1); - result.add(leaf); - unusedCerts.remove(leaf); - X509Certificate root = leaf; - while (!root.getSubjectDN().equals(root.getIssuerDN())) { - Principal targetDn = root.getIssuerDN(); - boolean issuerFound = false; - for (int i = 0; i < unusedCerts.size(); i++) { - X509Certificate unusedCert = unusedCerts.get(i); - if (targetDn.equals(unusedCert.getSubjectDN())) { - issuerFound = true; - unusedCerts.remove(i); - result.add(unusedCert); - root = unusedCert; - break; - } - } - if (!issuerFound) { - break; - } - } - return result; - } - - - - - public void verifySigFileAgainstManifest( - byte[] manifestBytes, - ManifestParser.Section manifestMainSection, - Map entryNameToManifestSection, - Map supportedApkSigSchemeNames, - Set 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 sfSections = sf.readAllSections(); - Set 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 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, - V1SchemeConstants.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 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 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 supportedApkSigSchemeNames, - Set foundApkSigSchemeIds) { - String signedWithApkSchemes = - sfMainSection.getAttributeValue( - V1SchemeConstants.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 supportedApkSigSchemeIds = supportedApkSigSchemeNames.keySet(); - Set 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); - } - } - } - } - - public static Collection getDigestsToVerify( - ManifestParser.Section section, - String digestAttrSuffix, - int minSdkVersion, - int maxSdkVersion) { - Decoder base64Decoder = Base64.getDecoder(); - List 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 final 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 final Map 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 final Map - 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 digests, String jcaDigestAlgorithm) { - for (NamedDigest digest : digests) { - if (digest.jcaDigestAlgorithm.equalsIgnoreCase(jcaDigestAlgorithm)) { - return digest.digest; - } - } - return null; - } - - public static List parseZipCentralDirectory( - DataSource apk, - ApkUtils.ZipSections apkSections) - throws IOException, ApkFormatException { - return ZipUtils.parseZipCentralDirectory(apk, apkSections); - } - - /** - * 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 verifyJarEntriesAgainstManifestAndSigners( - DataSource apk, - long cdOffsetInApk, - Collection cdRecords, - Map entryNameToManifestSection, - List signers, - int minSdkVersion, - int maxSdkVersion, - Result result) throws ApkFormatException, IOException, NoSuchAlgorithmException { - // Iterate over APK contents as sequentially as possible to improve performance. - List cdRecordsSortedByLocalFileHeaderOffset = - new ArrayList<>(cdRecords); - Collections.sort( - cdRecordsSortedByLocalFileHeaderOffset, - CentralDirectoryRecord.BY_LOCAL_FILE_HEADER_OFFSET_COMPARATOR); - List firstSignedEntrySigners = null; - String firstSignedEntryName = null; - for (CentralDirectoryRecord cdRecord : cdRecordsSortedByLocalFileHeaderOffset) { - String entryName = cdRecord.getName(); - 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 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; - } - - List expectedDigests = - new ArrayList<>( - 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()]; - for (int i = 0; i < expectedDigests.size(); i++) { - mds[i] = getMessageDigest(expectedDigests.get(i).jcaDigestAlgorithm); - } - - try { - LocalFileRecord.outputUncompressedData( - apk, - cdRecord, - cdOffsetInApk, - DataSinks.asDataSink(mds)); - } catch (ZipFormatException e) { - throw new ApkFormatException("Malformed ZIP entry: " + entryName, e); - } catch (IOException e) { - throw new IOException("Failed to read entry: " + entryName, e); - } - - for (int i = 0; i < expectedDigests.size(); i++) { - NamedDigest expectedDigest = expectedDigests.get(i); - byte[] actualDigest = mds[i].digest(); - if (!Arrays.equals(expectedDigest.digest, actualDigest)) { - result.addError( - Issue.JAR_SIG_ZIP_ENTRY_DIGEST_DID_NOT_VERIFY, - entryName, - expectedDigest.jcaDigestAlgorithm, - V1SchemeConstants.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 getSignerNames(List signers) { - if (signers.isEmpty()) { - return Collections.emptyList(); - } - List 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); - } - - public static class NamedDigest { - public final String jcaDigestAlgorithm; - public 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 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 ignoredSigners = new ArrayList<>(); - - private final List mWarnings = new ArrayList<>(); - private final List 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 getErrors() { - return mErrors; - } - - public List getWarnings() { - return mWarnings; - } - - public static class SignerInfo { - public final String name; - public final String signatureFileName; - public final String signatureBlockFileName; - public final List certChain = new ArrayList<>(); - - private final List mWarnings = new ArrayList<>(); - private final List 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 getErrors() { - return mErrors; - } - - public List getWarnings() { - return mWarnings; - } - } - } - - private static class SignedAttributes { - private Map> mAttrs; - - public SignedAttributes(Collection attrs) throws Pkcs7DecodingException { - Map> result = new HashMap<>(attrs.size()); - for (Attribute attr : attrs) { - if (result.put(attr.attrType, attr.attrValues) != null) { - throw new Pkcs7DecodingException("Duplicate signed attribute: " + attr.attrType); - } - } - mAttrs = result; - } - - private Asn1OpaqueObject getSingleValue(String attrOid) throws Pkcs7DecodingException { - List values = mAttrs.get(attrOid); - if ((values == null) || (values.isEmpty())) { - return null; - } - if (values.size() > 1) { - throw new Pkcs7DecodingException("Attribute " + attrOid + " has multiple values"); - } - return values.get(0); - } - - public String getSingleObjectIdentifierValue(String attrOid) throws Pkcs7DecodingException { - Asn1OpaqueObject value = getSingleValue(attrOid); - if (value == null) { - return null; - } - try { - return Asn1BerParser.parse(value.getEncoded(), ObjectIdentifierChoice.class).value; - } catch (Asn1DecodingException e) { - throw new Pkcs7DecodingException("Failed to decode OBJECT IDENTIFIER", e); - } - } - - public byte[] getSingleOctetStringValue(String attrOid) throws Pkcs7DecodingException { - Asn1OpaqueObject value = getSingleValue(attrOid); - if (value == null) { - return null; - } - try { - return Asn1BerParser.parse(value.getEncoded(), OctetStringChoice.class).value; - } catch (Asn1DecodingException e) { - throw new Pkcs7DecodingException("Failed to decode OBJECT IDENTIFIER", e); - } - } - } - - @Asn1Class(type = Asn1Type.CHOICE) - public static class OctetStringChoice { - @Asn1Field(type = Asn1Type.OCTET_STRING) - public byte[] value; - } - - @Asn1Class(type = Asn1Type.CHOICE) - public static class ObjectIdentifierChoice { - @Asn1Field(type = Asn1Type.OBJECT_IDENTIFIER) - public String value; - } -} diff --git a/app/src/main/java/com/android/apksig/internal/apk/v2/V2SchemeConstants.java b/app/src/main/java/com/android/apksig/internal/apk/v2/V2SchemeConstants.java deleted file mode 100644 index 0e244c8373..0000000000 --- a/app/src/main/java/com/android/apksig/internal/apk/v2/V2SchemeConstants.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.apk.v2; - -/** Constants used by the V2 Signature Scheme signing and verification. */ -public class V2SchemeConstants { - private V2SchemeConstants() {} - - public static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a; - public static final int STRIPPING_PROTECTION_ATTR_ID = 0xbeeff00d; -} diff --git a/app/src/main/java/com/android/apksig/internal/apk/v2/V2SchemeSigner.java b/app/src/main/java/com/android/apksig/internal/apk/v2/V2SchemeSigner.java deleted file mode 100644 index b69b7d3df5..0000000000 --- a/app/src/main/java/com/android/apksig/internal/apk/v2/V2SchemeSigner.java +++ /dev/null @@ -1,321 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.apk.v2; - -import static com.android.apksig.internal.apk.ApkSigningBlockUtils.encodeAsSequenceOfLengthPrefixedElements; -import static com.android.apksig.internal.apk.ApkSigningBlockUtils.encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes; -import static com.android.apksig.internal.apk.ApkSigningBlockUtils.encodeCertificates; -import static com.android.apksig.internal.apk.ApkSigningBlockUtils.encodePublicKey; - -import com.android.apksig.internal.apk.ApkSigningBlockUtils; -import com.android.apksig.internal.apk.ApkSigningBlockUtils.SignerConfig; -import com.android.apksig.internal.apk.ContentDigestAlgorithm; -import com.android.apksig.internal.apk.SignatureAlgorithm; -import com.android.apksig.internal.util.Pair; -import com.android.apksig.util.DataSource; -import com.android.apksig.util.RunnablesExecutor; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; -import java.security.SignatureException; -import java.security.cert.CertificateEncodingException; -import java.security.interfaces.ECKey; -import java.security.interfaces.RSAKey; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -/** - * APK Signature Scheme v2 signer. - * - *

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. - * - * @see APK Signature Scheme v2 - */ -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. - */ - - public static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = - V2SchemeConstants.APK_SIGNATURE_SCHEME_V2_BLOCK_ID; - - /** 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 getSuggestedSignatureAlgorithms(PublicKey signingKey, - int minSdkVersion, boolean verityEnabled, boolean deterministicDsaSigning) - 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. - List algorithms = new ArrayList<>(); - algorithms.add(SignatureAlgorithm.RSA_PKCS1_V1_5_WITH_SHA256); - if (verityEnabled) { - algorithms.add(SignatureAlgorithm.VERITY_RSA_PKCS1_V1_5_WITH_SHA256); - } - return algorithms; - } 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. - List algorithms = new ArrayList<>(); - algorithms.add( - deterministicDsaSigning ? - SignatureAlgorithm.DETDSA_WITH_SHA256 : - SignatureAlgorithm.DSA_WITH_SHA256); - if (verityEnabled) { - algorithms.add(SignatureAlgorithm.VERITY_DSA_WITH_SHA256); - } - return algorithms; - } 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. - List algorithms = new ArrayList<>(); - algorithms.add(SignatureAlgorithm.ECDSA_WITH_SHA256); - if (verityEnabled) { - algorithms.add(SignatureAlgorithm.VERITY_ECDSA_WITH_SHA256); - } - return algorithms; - } 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); - } - } - - public static ApkSigningBlockUtils.SigningSchemeBlockAndDigests - generateApkSignatureSchemeV2Block(RunnablesExecutor executor, - DataSource beforeCentralDir, - DataSource centralDir, - DataSource eocd, - List signerConfigs, - boolean v3SigningEnabled) - throws IOException, InvalidKeyException, NoSuchAlgorithmException, - SignatureException { - return generateApkSignatureSchemeV2Block(executor, beforeCentralDir, centralDir, eocd, - signerConfigs, v3SigningEnabled, null); - } - - public static ApkSigningBlockUtils.SigningSchemeBlockAndDigests - generateApkSignatureSchemeV2Block( - RunnablesExecutor executor, - DataSource beforeCentralDir, - DataSource centralDir, - DataSource eocd, - List signerConfigs, - boolean v3SigningEnabled, - List preservedV2SignerBlocks) - throws IOException, InvalidKeyException, NoSuchAlgorithmException, - SignatureException { - Pair, Map> digestInfo = - ApkSigningBlockUtils.computeContentDigests( - executor, beforeCentralDir, centralDir, eocd, signerConfigs); - return new ApkSigningBlockUtils.SigningSchemeBlockAndDigests( - generateApkSignatureSchemeV2Block( - digestInfo.getFirst(), digestInfo.getSecond(), v3SigningEnabled, - preservedV2SignerBlocks), - digestInfo.getSecond()); - } - - private static Pair generateApkSignatureSchemeV2Block( - List signerConfigs, - Map contentDigests, - boolean v3SigningEnabled, - List preservedV2SignerBlocks) - throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { - // FORMAT: - // * length-prefixed sequence of length-prefixed signer blocks. - - List signerBlocks = new ArrayList<>(signerConfigs.size()); - if (preservedV2SignerBlocks != null && preservedV2SignerBlocks.size() > 0) { - signerBlocks.addAll(preservedV2SignerBlocks); - } - int signerNumber = 0; - for (SignerConfig signerConfig : signerConfigs) { - signerNumber++; - byte[] signerBlock; - try { - signerBlock = generateSignerBlock(signerConfig, contentDigests, v3SigningEnabled); - } catch (InvalidKeyException e) { - throw new InvalidKeyException("Signer #" + signerNumber + " failed", e); - } catch (SignatureException e) { - throw new SignatureException("Signer #" + signerNumber + " failed", e); - } - signerBlocks.add(signerBlock); - } - - return Pair.of( - encodeAsSequenceOfLengthPrefixedElements( - new byte[][] { - encodeAsSequenceOfLengthPrefixedElements(signerBlocks), - }), - V2SchemeConstants.APK_SIGNATURE_SCHEME_V2_BLOCK_ID); - } - - private static byte[] generateSignerBlock( - SignerConfig signerConfig, - Map contentDigests, - boolean v3SigningEnabled) - 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> 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; - signedData.additionalAttributes = generateAdditionalAttributes(v3SigningEnabled); - - 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), - signedData.additionalAttributes, - new byte[0], - }); - signer.publicKey = encodedPublicKey; - signer.signatures = new ArrayList<>(); - signer.signatures = - ApkSigningBlockUtils.generateSignaturesOverData(signerConfig, signer.signedData); - - // 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 byte[] generateAdditionalAttributes(boolean v3SigningEnabled) { - if (v3SigningEnabled) { - // FORMAT (little endian): - // * length-prefixed bytes: attribute pair - // * uint32: ID - STRIPPING_PROTECTION_ATTR_ID in this case - // * uint32: value - 3 (v3 signature scheme id) in this case - int payloadSize = 4 + 4 + 4; - ByteBuffer result = ByteBuffer.allocate(payloadSize); - result.order(ByteOrder.LITTLE_ENDIAN); - result.putInt(payloadSize - 4); - result.putInt(V2SchemeConstants.STRIPPING_PROTECTION_ATTR_ID); - result.putInt(ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3); - return result.array(); - } else { - return new byte[0]; - } - } - - private static final class V2SignatureSchemeBlock { - private static final class Signer { - public byte[] signedData; - public List> signatures; - public byte[] publicKey; - } - - private static final class SignedData { - public List> digests; - public List certificates; - public byte[] additionalAttributes; - } - } -} diff --git a/app/src/main/java/com/android/apksig/internal/apk/v2/V2SchemeVerifier.java b/app/src/main/java/com/android/apksig/internal/apk/v2/V2SchemeVerifier.java deleted file mode 100644 index f36790855f..0000000000 --- a/app/src/main/java/com/android/apksig/internal/apk/v2/V2SchemeVerifier.java +++ /dev/null @@ -1,466 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.apk.v2; - -import com.android.apksig.ApkVerifier.Issue; -import com.android.apksig.apk.ApkFormatException; -import com.android.apksig.apk.ApkUtils; -import com.android.apksig.internal.apk.ApkSigningBlockUtils; -import com.android.apksig.internal.apk.ContentDigestAlgorithm; -import com.android.apksig.internal.apk.SignatureAlgorithm; -import com.android.apksig.internal.apk.SignatureInfo; -import com.android.apksig.internal.util.ByteBufferUtils; -import com.android.apksig.internal.util.X509CertificateUtils; -import com.android.apksig.internal.util.GuaranteedEncodedFormX509Certificate; -import com.android.apksig.util.DataSource; -import com.android.apksig.util.RunnablesExecutor; -import java.io.IOException; -import java.nio.BufferUnderflowException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -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.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.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * APK Signature Scheme v2 verifier. - * - *

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. - * - * @see APK Signature Scheme v2 - */ -public abstract class V2SchemeVerifier { - /** Hidden constructor to prevent instantiation. */ - private V2SchemeVerifier() {} - - /** - * Verifies the provided APK's APK Signature Scheme v2 signatures and returns the result of - * verification. The APK must be considered verified only if - * {@link ApkSigningBlockUtils.Result#verified} is - * {@code true}. If verification fails, the result will contain errors -- see - * {@link ApkSigningBlockUtils.Result#getErrors()}. - * - *

Verification succeeds iff the APK's APK Signature Scheme v2 signatures are expected to - * verify on all Android platform versions in the {@code [minSdkVersion, maxSdkVersion]} range. - * If the APK's signature is expected to not verify on any of the specified platform versions, - * this method returns a result with one or more errors and whose - * {@code Result.verified == false}, or this method throws an exception. - * - * @throws ApkFormatException if the APK is malformed - * @throws NoSuchAlgorithmException if the APK's signatures cannot be verified because a - * required cryptographic algorithm implementation is missing - * @throws ApkSigningBlockUtils.SignatureNotFoundException if no APK Signature Scheme v2 - * signatures are found - * @throws IOException if an I/O error occurs when reading the APK - */ - public static ApkSigningBlockUtils.Result verify( - RunnablesExecutor executor, - DataSource apk, - ApkUtils.ZipSections zipSections, - Map supportedApkSigSchemeNames, - Set foundSigSchemeIds, - int minSdkVersion, - int maxSdkVersion) - throws IOException, ApkFormatException, NoSuchAlgorithmException, - ApkSigningBlockUtils.SignatureNotFoundException { - ApkSigningBlockUtils.Result result = new ApkSigningBlockUtils.Result( - ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2); - SignatureInfo signatureInfo = - ApkSigningBlockUtils.findSignature(apk, zipSections, - V2SchemeConstants.APK_SIGNATURE_SCHEME_V2_BLOCK_ID , result); - - DataSource beforeApkSigningBlock = apk.slice(0, signatureInfo.apkSigningBlockOffset); - DataSource centralDir = - apk.slice( - signatureInfo.centralDirOffset, - signatureInfo.eocdOffset - signatureInfo.centralDirOffset); - ByteBuffer eocd = signatureInfo.eocd; - - verify(executor, - beforeApkSigningBlock, - signatureInfo.signatureBlock, - centralDir, - eocd, - supportedApkSigSchemeNames, - foundSigSchemeIds, - minSdkVersion, - maxSdkVersion, - 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}. See {@link #verify(RunnablesExecutor, DataSource, ApkUtils.ZipSections, Map, - * Set, int, int)} for more information about the contract of this method. - * - * @param result result populated by this method with interesting information about the APK, - * such as information about signers, and verification errors and warnings. - */ - private static void verify( - RunnablesExecutor executor, - DataSource beforeApkSigningBlock, - ByteBuffer apkSignatureSchemeV2Block, - DataSource centralDir, - ByteBuffer eocd, - Map supportedApkSigSchemeNames, - Set foundSigSchemeIds, - int minSdkVersion, - int maxSdkVersion, - ApkSigningBlockUtils.Result result) - throws IOException, NoSuchAlgorithmException { - Set contentDigestsToVerify = new HashSet<>(1); - parseSigners( - apkSignatureSchemeV2Block, - contentDigestsToVerify, - supportedApkSigSchemeNames, - foundSigSchemeIds, - minSdkVersion, - maxSdkVersion, - result); - if (result.containsErrors()) { - return; - } - ApkSigningBlockUtils.verifyIntegrity( - executor, beforeApkSigningBlock, centralDir, eocd, contentDigestsToVerify, result); - if (!result.containsErrors()) { - result.verified = true; - } - } - - /** - * Parses each signer in the provided APK Signature Scheme v2 block and populates corresponding - * {@code signerInfos} of the provided {@code result}. - * - *

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}). - * - *

This method adds one or more errors to the {@code result} if a verification error is - * expected to be encountered on an Android platform version in the - * {@code [minSdkVersion, maxSdkVersion]} range. - */ - public static void parseSigners( - ByteBuffer apkSignatureSchemeV2Block, - Set contentDigestsToVerify, - Map supportedApkSigSchemeNames, - Set foundApkSigSchemeIds, - int minSdkVersion, - int maxSdkVersion, - ApkSigningBlockUtils.Result result) throws NoSuchAlgorithmException { - ByteBuffer signers; - try { - signers = ApkSigningBlockUtils.getLengthPrefixedSlice(apkSignatureSchemeV2Block); - } catch (ApkFormatException 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++; - ApkSigningBlockUtils.Result.SignerInfo signerInfo = - new ApkSigningBlockUtils.Result.SignerInfo(); - signerInfo.index = signerIndex; - result.signers.add(signerInfo); - try { - ByteBuffer signer = ApkSigningBlockUtils.getLengthPrefixedSlice(signers); - parseSigner( - signer, - certFactory, - signerInfo, - contentDigestsToVerify, - supportedApkSigSchemeNames, - foundApkSigSchemeIds, - minSdkVersion, - maxSdkVersion); - } catch (ApkFormatException | BufferUnderflowException e) { - signerInfo.addError(Issue.V2_SIG_MALFORMED_SIGNER); - return; - } - } - } - - /** - * Parses the provided signer block and populates the {@code result}. - * - *

This verifies signatures over {@code signed-data} contained in this block but does not - * verify the integrity of the rest of the APK. To facilitate APK integrity verification, this - * method adds the {@code contentDigestsToVerify}. These digests can then be used to verify the - * integrity of the APK. - * - *

This method adds one or more errors to the {@code result} if a verification error is - * expected to be encountered on an Android platform version in the - * {@code [minSdkVersion, maxSdkVersion]} range. - */ - private static void parseSigner( - ByteBuffer signerBlock, - CertificateFactory certFactory, - ApkSigningBlockUtils.Result.SignerInfo result, - Set contentDigestsToVerify, - Map supportedApkSigSchemeNames, - Set foundApkSigSchemeIds, - int minSdkVersion, - int maxSdkVersion) throws ApkFormatException, NoSuchAlgorithmException { - ByteBuffer signedData = ApkSigningBlockUtils.getLengthPrefixedSlice(signerBlock); - byte[] signedDataBytes = new byte[signedData.remaining()]; - signedData.get(signedDataBytes); - signedData.flip(); - result.signedData = signedDataBytes; - - ByteBuffer signatures = ApkSigningBlockUtils.getLengthPrefixedSlice(signerBlock); - byte[] publicKeyBytes = ApkSigningBlockUtils.readLengthPrefixedByteArray(signerBlock); - - // Parse the signatures block and identify supported signatures - int signatureCount = 0; - List supportedSignatures = new ArrayList<>(1); - while (signatures.hasRemaining()) { - signatureCount++; - try { - ByteBuffer signature = ApkSigningBlockUtils.getLengthPrefixedSlice(signatures); - int sigAlgorithmId = signature.getInt(); - byte[] sigBytes = ApkSigningBlockUtils.readLengthPrefixedByteArray(signature); - result.signatures.add( - new ApkSigningBlockUtils.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 ApkSigningBlockUtils.SupportedSignature(signatureAlgorithm, sigBytes)); - } catch (ApkFormatException | 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 signaturesToVerify = null; - try { - signaturesToVerify = - ApkSigningBlockUtils.getSignaturesToVerify( - supportedSignatures, minSdkVersion, maxSdkVersion); - } catch (ApkSigningBlockUtils.NoSupportedSignaturesException e) { - result.addError(Issue.V2_SIG_NO_SUPPORTED_SIGNATURES, e); - return; - } - for (ApkSigningBlockUtils.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 = ApkSigningBlockUtils.getLengthPrefixedSlice(signedData); - ByteBuffer certificates = ApkSigningBlockUtils.getLengthPrefixedSlice(signedData); - ByteBuffer additionalAttributes = ApkSigningBlockUtils.getLengthPrefixedSlice(signedData); - - // Parse the certificates block - int certificateIndex = -1; - while (certificates.hasRemaining()) { - certificateIndex++; - byte[] encodedCert = ApkSigningBlockUtils.readLengthPrefixedByteArray(certificates); - X509Certificate certificate; - try { - certificate = X509CertificateUtils.generateCertificate(encodedCert, certFactory); - } 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 because 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; - try { - certificatePublicKeyBytes = ApkSigningBlockUtils.encodePublicKey( - mainCertificate.getPublicKey()); - } catch (InvalidKeyException e) { - System.out.println("Caught an exception encoding the public key: " + e); - e.printStackTrace(); - certificatePublicKeyBytes = mainCertificate.getPublicKey().getEncoded(); - } - if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) { - result.addError( - Issue.V2_SIG_PUBLIC_KEY_MISMATCH_BETWEEN_CERTIFICATE_AND_SIGNATURES_RECORD, - ApkSigningBlockUtils.toHex(certificatePublicKeyBytes), - ApkSigningBlockUtils.toHex(publicKeyBytes)); - return; - } - - // Parse the digests block - int digestCount = 0; - while (digests.hasRemaining()) { - digestCount++; - try { - ByteBuffer digest = ApkSigningBlockUtils.getLengthPrefixedSlice(digests); - int sigAlgorithmId = digest.getInt(); - byte[] digestBytes = ApkSigningBlockUtils.readLengthPrefixedByteArray(digest); - result.contentDigests.add( - new ApkSigningBlockUtils.Result.SignerInfo.ContentDigest( - sigAlgorithmId, digestBytes)); - } catch (ApkFormatException | BufferUnderflowException e) { - result.addError(Issue.V2_SIG_MALFORMED_DIGEST, digestCount); - return; - } - } - - List sigAlgsFromSignaturesRecord = new ArrayList<>(result.signatures.size()); - for (ApkSigningBlockUtils.Result.SignerInfo.Signature signature : result.signatures) { - sigAlgsFromSignaturesRecord.add(signature.getAlgorithmId()); - } - List sigAlgsFromDigestsRecord = new ArrayList<>(result.contentDigests.size()); - for (ApkSigningBlockUtils.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; - Set supportedApkSigSchemeIds = supportedApkSigSchemeNames.keySet(); - Set supportedExpectedApkSigSchemeIds = new HashSet<>(1); - while (additionalAttributes.hasRemaining()) { - additionalAttributeCount++; - try { - ByteBuffer attribute = - ApkSigningBlockUtils.getLengthPrefixedSlice(additionalAttributes); - int id = attribute.getInt(); - byte[] value = ByteBufferUtils.toByteArray(attribute); - result.additionalAttributes.add( - new ApkSigningBlockUtils.Result.SignerInfo.AdditionalAttribute(id, value)); - switch (id) { - case V2SchemeConstants.STRIPPING_PROTECTION_ATTR_ID: - // stripping protection added when signing with a newer scheme - int foundId = ByteBuffer.wrap(value).order( - ByteOrder.LITTLE_ENDIAN).getInt(); - if (supportedApkSigSchemeIds.contains(foundId)) { - supportedExpectedApkSigSchemeIds.add(foundId); - } else { - result.addWarning( - Issue.V2_SIG_UNKNOWN_APK_SIG_SCHEME_ID, result.index, foundId); - } - break; - default: - result.addWarning(Issue.V2_SIG_UNKNOWN_ADDITIONAL_ATTRIBUTE, id); - } - } catch (ApkFormatException | BufferUnderflowException e) { - result.addError( - Issue.V2_SIG_MALFORMED_ADDITIONAL_ATTRIBUTE, additionalAttributeCount); - return; - } - } - - // make sure that all known IDs indicated in stripping protection have already verified - for (int id : supportedExpectedApkSigSchemeIds) { - if (!foundApkSigSchemeIds.contains(id)) { - String apkSigSchemeName = supportedApkSigSchemeNames.get(id); - result.addError( - Issue.V2_SIG_MISSING_APK_SIG_REFERENCED, - result.index, - apkSigSchemeName); - } - } - } -} diff --git a/app/src/main/java/com/android/apksig/internal/apk/v3/V3SchemeConstants.java b/app/src/main/java/com/android/apksig/internal/apk/v3/V3SchemeConstants.java deleted file mode 100644 index 3b70aa06bc..0000000000 --- a/app/src/main/java/com/android/apksig/internal/apk/v3/V3SchemeConstants.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.apk.v3; - -/** Constants used by the V3 Signature Scheme signing and verification. */ -public class V3SchemeConstants { - private V3SchemeConstants() {} - - public static final int APK_SIGNATURE_SCHEME_V3_BLOCK_ID = 0xf05368c0; - public static final int PROOF_OF_ROTATION_ATTR_ID = 0x3ba06f8c; -} diff --git a/app/src/main/java/com/android/apksig/internal/apk/v3/V3SchemeSigner.java b/app/src/main/java/com/android/apksig/internal/apk/v3/V3SchemeSigner.java deleted file mode 100644 index 04260d582c..0000000000 --- a/app/src/main/java/com/android/apksig/internal/apk/v3/V3SchemeSigner.java +++ /dev/null @@ -1,332 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.apk.v3; - -import static com.android.apksig.internal.apk.ApkSigningBlockUtils.encodeAsLengthPrefixedElement; -import static com.android.apksig.internal.apk.ApkSigningBlockUtils.encodeAsSequenceOfLengthPrefixedElements; -import static com.android.apksig.internal.apk.ApkSigningBlockUtils.encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes; -import static com.android.apksig.internal.apk.ApkSigningBlockUtils.encodeCertificates; -import static com.android.apksig.internal.apk.ApkSigningBlockUtils.encodePublicKey; - -import com.android.apksig.SigningCertificateLineage; -import com.android.apksig.internal.apk.ApkSigningBlockUtils; -import com.android.apksig.internal.apk.ApkSigningBlockUtils.SignerConfig; -import com.android.apksig.internal.apk.ContentDigestAlgorithm; -import com.android.apksig.internal.apk.SignatureAlgorithm; -import com.android.apksig.internal.util.Pair; -import com.android.apksig.util.DataSource; -import com.android.apksig.util.RunnablesExecutor; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; -import java.security.SignatureException; -import java.security.cert.CertificateEncodingException; -import java.security.interfaces.ECKey; -import java.security.interfaces.RSAKey; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -/** - * APK Signature Scheme v3 signer. - * - *

APK Signature Scheme v3 builds upon APK Signature Scheme v3, and maintains all of the APK - * Signature Scheme v2 goals. - * - * @see APK Signature Scheme v2 - *

The main contribution of APK Signature Scheme v3 is the introduction of the {@link - * SigningCertificateLineage}, which enables an APK to change its signing certificate as long as - * it can prove the new siging certificate was signed by the old. - */ -public abstract class V3SchemeSigner { - public static final int APK_SIGNATURE_SCHEME_V3_BLOCK_ID = - V3SchemeConstants.APK_SIGNATURE_SCHEME_V3_BLOCK_ID; - public static final int PROOF_OF_ROTATION_ATTR_ID = V3SchemeConstants.PROOF_OF_ROTATION_ATTR_ID; - - /** Hidden constructor to prevent instantiation. */ - private V3SchemeSigner() {} - - /** - * Gets the APK Signature Scheme v3 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 v3 - */ - public static List getSuggestedSignatureAlgorithms(PublicKey signingKey, - int minSdkVersion, boolean verityEnabled, boolean deterministicDsaSigning) - 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. - List algorithms = new ArrayList<>(); - algorithms.add(SignatureAlgorithm.RSA_PKCS1_V1_5_WITH_SHA256); - if (verityEnabled) { - algorithms.add(SignatureAlgorithm.VERITY_RSA_PKCS1_V1_5_WITH_SHA256); - } - return algorithms; - } 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. - List algorithms = new ArrayList<>(); - algorithms.add( - deterministicDsaSigning ? - SignatureAlgorithm.DETDSA_WITH_SHA256 : - SignatureAlgorithm.DSA_WITH_SHA256); - if (verityEnabled) { - algorithms.add(SignatureAlgorithm.VERITY_DSA_WITH_SHA256); - } - return algorithms; - } 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. - List algorithms = new ArrayList<>(); - algorithms.add(SignatureAlgorithm.ECDSA_WITH_SHA256); - if (verityEnabled) { - algorithms.add(SignatureAlgorithm.VERITY_ECDSA_WITH_SHA256); - } - return algorithms; - } 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); - } - } - - public static ApkSigningBlockUtils.SigningSchemeBlockAndDigests - generateApkSignatureSchemeV3Block( - RunnablesExecutor executor, - DataSource beforeCentralDir, - DataSource centralDir, - DataSource eocd, - List signerConfigs) - throws IOException, InvalidKeyException, NoSuchAlgorithmException, - SignatureException { - Pair, Map> digestInfo = - ApkSigningBlockUtils.computeContentDigests( - executor, beforeCentralDir, centralDir, eocd, signerConfigs); - return new ApkSigningBlockUtils.SigningSchemeBlockAndDigests( - generateApkSignatureSchemeV3Block(digestInfo.getFirst(), digestInfo.getSecond()), - digestInfo.getSecond()); - } - - public static byte[] generateV3SignerAttribute( - SigningCertificateLineage signingCertificateLineage) { - // FORMAT (little endian): - // * length-prefixed bytes: attribute pair - // * uint32: ID - // * bytes: value - encoded V3 SigningCertificateLineage - byte[] encodedLineage = signingCertificateLineage.encodeSigningCertificateLineage(); - int payloadSize = 4 + 4 + encodedLineage.length; - ByteBuffer result = ByteBuffer.allocate(payloadSize); - result.order(ByteOrder.LITTLE_ENDIAN); - result.putInt(4 + encodedLineage.length); - result.putInt(V3SchemeConstants.PROOF_OF_ROTATION_ATTR_ID); - result.put(encodedLineage); - return result.array(); - } - - private static Pair generateApkSignatureSchemeV3Block( - List signerConfigs, Map contentDigests) - throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { - // FORMAT: - // * length-prefixed sequence of length-prefixed signer blocks. - List 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 Pair.of( - encodeAsSequenceOfLengthPrefixedElements( - new byte[][] { - encodeAsSequenceOfLengthPrefixedElements(signerBlocks), - }), - V3SchemeConstants.APK_SIGNATURE_SCHEME_V3_BLOCK_ID); - } - - private static byte[] generateSignerBlock( - SignerConfig signerConfig, Map 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); - - V3SignatureSchemeBlock.SignedData signedData = new V3SignatureSchemeBlock.SignedData(); - try { - signedData.certificates = encodeCertificates(signerConfig.certificates); - } catch (CertificateEncodingException e) { - throw new SignatureException("Failed to encode certificates", e); - } - - List> 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; - signedData.minSdkVersion = signerConfig.minSdkVersion; - signedData.maxSdkVersion = signerConfig.maxSdkVersion; - signedData.additionalAttributes = generateAdditionalAttributes(signerConfig); - - V3SignatureSchemeBlock.Signer signer = new V3SignatureSchemeBlock.Signer(); - - signer.signedData = encodeSignedData(signedData); - - signer.minSdkVersion = signerConfig.minSdkVersion; - signer.maxSdkVersion = signerConfig.maxSdkVersion; - signer.publicKey = encodedPublicKey; - signer.signatures = - ApkSigningBlockUtils.generateSignaturesOverData(signerConfig, signer.signedData); - - return encodeSigner(signer); - } - - private static byte[] encodeSigner(V3SignatureSchemeBlock.Signer signer) { - byte[] signedData = encodeAsLengthPrefixedElement(signer.signedData); - byte[] signatures = - encodeAsLengthPrefixedElement( - encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes( - signer.signatures)); - byte[] publicKey = encodeAsLengthPrefixedElement(signer.publicKey); - - // FORMAT: - // * length-prefixed signed data - // * uint32: minSdkVersion - // * uint32: maxSdkVersion - // * 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) - int payloadSize = signedData.length + 4 + 4 + signatures.length + publicKey.length; - - ByteBuffer result = ByteBuffer.allocate(payloadSize); - result.order(ByteOrder.LITTLE_ENDIAN); - result.put(signedData); - result.putInt(signer.minSdkVersion); - result.putInt(signer.maxSdkVersion); - result.put(signatures); - result.put(publicKey); - - return result.array(); - } - - private static byte[] encodeSignedData(V3SignatureSchemeBlock.SignedData signedData) { - byte[] digests = - encodeAsLengthPrefixedElement( - encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes( - signedData.digests)); - byte[] certs = - encodeAsLengthPrefixedElement( - encodeAsSequenceOfLengthPrefixedElements(signedData.certificates)); - byte[] attributes = encodeAsLengthPrefixedElement(signedData.additionalAttributes); - - // 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). - // * uint-32: minSdkVersion - // * uint-32: maxSdkVersion - // * length-prefixed sequence of length-prefixed additional attributes: - // * uint32: ID - // * (length - 4) bytes: value - // * uint32: Proof-of-rotation ID: 0x3ba06f8c - // * length-prefixed roof-of-rotation structure - int payloadSize = digests.length + certs.length + 4 + 4 + attributes.length; - - ByteBuffer result = ByteBuffer.allocate(payloadSize); - result.order(ByteOrder.LITTLE_ENDIAN); - result.put(digests); - result.put(certs); - result.putInt(signedData.minSdkVersion); - result.putInt(signedData.maxSdkVersion); - result.put(attributes); - - return result.array(); - } - - private static byte[] generateAdditionalAttributes(SignerConfig signerConfig) { - if (signerConfig.mSigningCertificateLineage == null) { - return new byte[0]; - } - return generateV3SignerAttribute(signerConfig.mSigningCertificateLineage); - } - - private static final class V3SignatureSchemeBlock { - private static final class Signer { - public byte[] signedData; - public int minSdkVersion; - public int maxSdkVersion; - public List> signatures; - public byte[] publicKey; - } - - private static final class SignedData { - public List> digests; - public List certificates; - public int minSdkVersion; - public int maxSdkVersion; - public byte[] additionalAttributes; - } - } -} diff --git a/app/src/main/java/com/android/apksig/internal/apk/v3/V3SchemeVerifier.java b/app/src/main/java/com/android/apksig/internal/apk/v3/V3SchemeVerifier.java deleted file mode 100644 index ea931940e0..0000000000 --- a/app/src/main/java/com/android/apksig/internal/apk/v3/V3SchemeVerifier.java +++ /dev/null @@ -1,522 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.apk.v3; - -import static com.android.apksig.internal.apk.ApkSigningBlockUtils.getLengthPrefixedSlice; -import static com.android.apksig.internal.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray; - -import com.android.apksig.ApkVerifier.Issue; -import com.android.apksig.SigningCertificateLineage; -import com.android.apksig.apk.ApkFormatException; -import com.android.apksig.apk.ApkUtils; -import com.android.apksig.internal.apk.ApkSigningBlockUtils; -import com.android.apksig.internal.apk.ApkSigningBlockUtils.SignatureNotFoundException; -import com.android.apksig.internal.apk.ContentDigestAlgorithm; -import com.android.apksig.internal.apk.SignatureAlgorithm; -import com.android.apksig.internal.apk.SignatureInfo; -import com.android.apksig.internal.util.AndroidSdkVersion; -import com.android.apksig.internal.util.ByteBufferUtils; -import com.android.apksig.internal.util.GuaranteedEncodedFormX509Certificate; -import com.android.apksig.internal.util.X509CertificateUtils; -import com.android.apksig.util.DataSource; -import com.android.apksig.util.RunnablesExecutor; - -import java.io.IOException; -import java.nio.BufferUnderflowException; -import java.nio.ByteBuffer; -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.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.HashSet; -import java.util.List; -import java.util.Set; -import java.util.SortedMap; -import java.util.TreeMap; - -/** - * APK Signature Scheme v3 verifier. - * - *

APK Signature Scheme v3, like 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. - * - * @see APK Signature Scheme v2 - */ -public abstract class V3SchemeVerifier { - /** Hidden constructor to prevent instantiation. */ - private V3SchemeVerifier() {} - - /** - * Verifies the provided APK's APK Signature Scheme v3 signatures and returns the result of - * verification. The APK must be considered verified only if - * {@link ApkSigningBlockUtils.Result#verified} is - * {@code true}. If verification fails, the result will contain errors -- see - * {@link ApkSigningBlockUtils.Result#getErrors()}. - * - *

Verification succeeds iff the APK's APK Signature Scheme v3 signatures are expected to - * verify on all Android platform versions in the {@code [minSdkVersion, maxSdkVersion]} range. - * If the APK's signature is expected to not verify on any of the specified platform versions, - * this method returns a result with one or more errors and whose - * {@code Result.verified == false}, or this method throws an exception. - * - * @throws ApkFormatException if the APK is malformed - * @throws NoSuchAlgorithmException if the APK's signatures cannot be verified because a - * required cryptographic algorithm implementation is missing - * @throws SignatureNotFoundException if no APK Signature Scheme v3 - * signatures are found - * @throws IOException if an I/O error occurs when reading the APK - */ - public static ApkSigningBlockUtils.Result verify( - RunnablesExecutor executor, - DataSource apk, - ApkUtils.ZipSections zipSections, - int minSdkVersion, - int maxSdkVersion) - throws IOException, NoSuchAlgorithmException, SignatureNotFoundException { - ApkSigningBlockUtils.Result result = new ApkSigningBlockUtils.Result( - ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3); - SignatureInfo signatureInfo = - ApkSigningBlockUtils.findSignature(apk, zipSections, - V3SchemeConstants.APK_SIGNATURE_SCHEME_V3_BLOCK_ID, result); - - DataSource beforeApkSigningBlock = apk.slice(0, signatureInfo.apkSigningBlockOffset); - DataSource centralDir = - apk.slice( - signatureInfo.centralDirOffset, - signatureInfo.eocdOffset - signatureInfo.centralDirOffset); - ByteBuffer eocd = signatureInfo.eocd; - - // v3 didn't exist prior to P, so make sure that we're only judging v3 on its supported - // platforms - if (minSdkVersion < AndroidSdkVersion.P) { - minSdkVersion = AndroidSdkVersion.P; - } - - verify(executor, - beforeApkSigningBlock, - signatureInfo.signatureBlock, - centralDir, - eocd, - minSdkVersion, - maxSdkVersion, - result); - return result; - } - - /** - * Verifies the provided APK's v3 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}. See {@link #verify(RunnablesExecutor, DataSource, ApkUtils.ZipSections, int, - * int)} for more information about the contract of this method. - * - * @param result result populated by this method with interesting information about the APK, - * such as information about signers, and verification errors and warnings. - */ - private static void verify( - RunnablesExecutor executor, - DataSource beforeApkSigningBlock, - ByteBuffer apkSignatureSchemeV3Block, - DataSource centralDir, - ByteBuffer eocd, - int minSdkVersion, - int maxSdkVersion, - ApkSigningBlockUtils.Result result) - throws IOException, NoSuchAlgorithmException { - Set contentDigestsToVerify = new HashSet<>(1); - parseSigners(apkSignatureSchemeV3Block, contentDigestsToVerify, result); - - if (result.containsErrors()) { - return; - } - ApkSigningBlockUtils.verifyIntegrity( - executor, beforeApkSigningBlock, centralDir, eocd, contentDigestsToVerify, result); - - // make sure that the v3 signers cover the entire targeted sdk version ranges and that the - // longest SigningCertificateHistory, if present, corresponds to the newest platform - // versions - SortedMap sortedSigners = new TreeMap<>(); - for (ApkSigningBlockUtils.Result.SignerInfo signer : result.signers) { - sortedSigners.put(signer.minSdkVersion, signer); - } - - // first make sure there is neither overlap nor holes - int firstMin = 0; - int lastMax = 0; - int lastLineageSize = 0; - - // while we're iterating through the signers, build up the list of lineages - List lineages = new ArrayList<>(result.signers.size()); - - for (ApkSigningBlockUtils.Result.SignerInfo signer : sortedSigners.values()) { - int currentMin = signer.minSdkVersion; - int currentMax = signer.maxSdkVersion; - if (firstMin == 0) { - // first round sets up our basis - firstMin = currentMin; - } else { - if (currentMin != lastMax + 1) { - result.addError(Issue.V3_INCONSISTENT_SDK_VERSIONS); - break; - } - } - lastMax = currentMax; - - // also, while we're here, make sure that the lineage sizes only increase - if (signer.signingCertificateLineage != null) { - int currLineageSize = signer.signingCertificateLineage.size(); - if (currLineageSize < lastLineageSize) { - result.addError(Issue.V3_INCONSISTENT_LINEAGES); - break; - } - lastLineageSize = currLineageSize; - lineages.add(signer.signingCertificateLineage); - } - } - - // make sure we support our desired sdk ranges - if (firstMin > minSdkVersion || lastMax < maxSdkVersion) { - result.addError(Issue.V3_MISSING_SDK_VERSIONS, firstMin, lastMax); - } - - try { - result.signingCertificateLineage = - SigningCertificateLineage.consolidateLineages(lineages); - } catch (IllegalArgumentException e) { - result.addError(Issue.V3_INCONSISTENT_LINEAGES); - } - if (!result.containsErrors()) { - result.verified = true; - } - } - - /** - * Parses each signer in the provided APK Signature Scheme v3 block and populates corresponding - * {@code signerInfos} of the provided {@code result}. - * - *

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}). - * - *

This method adds one or more errors to the {@code result} if a verification error is - * expected to be encountered on an Android platform version in the - * {@code [minSdkVersion, maxSdkVersion]} range. - */ - public static void parseSigners( - ByteBuffer apkSignatureSchemeV3Block, - Set contentDigestsToVerify, - ApkSigningBlockUtils.Result result) throws NoSuchAlgorithmException { - ByteBuffer signers; - try { - signers = getLengthPrefixedSlice(apkSignatureSchemeV3Block); - } catch (ApkFormatException e) { - result.addError(Issue.V3_SIG_MALFORMED_SIGNERS); - return; - } - if (!signers.hasRemaining()) { - result.addError(Issue.V3_SIG_NO_SIGNERS); - return; - } - - CertificateFactory certFactory; - try { - certFactory = CertificateFactory.getInstance("X.509"); - } catch (CertificateException e) { - throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e); - } - int signerCount = 0; - while (signers.hasRemaining()) { - int signerIndex = signerCount; - signerCount++; - ApkSigningBlockUtils.Result.SignerInfo signerInfo = - new ApkSigningBlockUtils.Result.SignerInfo(); - signerInfo.index = signerIndex; - result.signers.add(signerInfo); - try { - ByteBuffer signer = getLengthPrefixedSlice(signers); - parseSigner(signer, certFactory, signerInfo, contentDigestsToVerify); - } catch (ApkFormatException | BufferUnderflowException e) { - signerInfo.addError(Issue.V3_SIG_MALFORMED_SIGNER); - return; - } - } - } - - /** - * Parses the provided signer block and populates the {@code result}. - * - *

This verifies signatures over {@code signed-data} contained in this block, as well as - * the data contained therein, but does not verify the integrity of the rest of the APK. To - * facilitate APK integrity verification, this method adds the {@code contentDigestsToVerify}. - * These digests can then be used to verify the integrity of the APK. - * - *

This method adds one or more errors to the {@code result} if a verification error is - * expected to be encountered on an Android platform version in the - * {@code [minSdkVersion, maxSdkVersion]} range. - */ - private static void parseSigner( - ByteBuffer signerBlock, - CertificateFactory certFactory, - ApkSigningBlockUtils.Result.SignerInfo result, - Set contentDigestsToVerify) - throws ApkFormatException, NoSuchAlgorithmException { - ByteBuffer signedData = getLengthPrefixedSlice(signerBlock); - byte[] signedDataBytes = new byte[signedData.remaining()]; - signedData.get(signedDataBytes); - signedData.flip(); - result.signedData = signedDataBytes; - - int parsedMinSdkVersion = signerBlock.getInt(); - int parsedMaxSdkVersion = signerBlock.getInt(); - result.minSdkVersion = parsedMinSdkVersion; - result.maxSdkVersion = parsedMaxSdkVersion; - if (parsedMinSdkVersion < 0 || parsedMinSdkVersion > parsedMaxSdkVersion) { - result.addError( - Issue.V3_SIG_INVALID_SDK_VERSIONS, parsedMinSdkVersion, parsedMaxSdkVersion); - } - ByteBuffer signatures = getLengthPrefixedSlice(signerBlock); - byte[] publicKeyBytes = readLengthPrefixedByteArray(signerBlock); - - // Parse the signatures block and identify supported signatures - int signatureCount = 0; - List 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 ApkSigningBlockUtils.Result.SignerInfo.Signature( - sigAlgorithmId, sigBytes)); - SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.findById(sigAlgorithmId); - if (signatureAlgorithm == null) { - result.addWarning(Issue.V3_SIG_UNKNOWN_SIG_ALGORITHM, sigAlgorithmId); - continue; - } - // TODO consider dropping deprecated signatures for v3 or modifying - // getSignaturesToVerify (called below) - supportedSignatures.add( - new ApkSigningBlockUtils.SupportedSignature(signatureAlgorithm, sigBytes)); - } catch (ApkFormatException | BufferUnderflowException e) { - result.addError(Issue.V3_SIG_MALFORMED_SIGNATURE, signatureCount); - return; - } - } - if (result.signatures.isEmpty()) { - result.addError(Issue.V3_SIG_NO_SIGNATURES); - return; - } - - // Verify signatures over signed-data block using the public key - List signaturesToVerify = null; - try { - signaturesToVerify = - ApkSigningBlockUtils.getSignaturesToVerify( - supportedSignatures, result.minSdkVersion, result.maxSdkVersion); - } catch (ApkSigningBlockUtils.NoSupportedSignaturesException e) { - result.addError(Issue.V3_SIG_NO_SUPPORTED_SIGNATURES); - return; - } - for (ApkSigningBlockUtils.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.V3_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.V3_SIG_DID_NOT_VERIFY, signatureAlgorithm); - return; - } - result.verifiedSignatures.put(signatureAlgorithm, sigBytes); - contentDigestsToVerify.add(signatureAlgorithm.getContentDigestAlgorithm()); - } catch (InvalidKeyException | InvalidAlgorithmParameterException - | SignatureException e) { - result.addError(Issue.V3_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); - - int signedMinSdkVersion = signedData.getInt(); - if (signedMinSdkVersion != parsedMinSdkVersion) { - result.addError( - Issue.V3_MIN_SDK_VERSION_MISMATCH_BETWEEN_SIGNER_AND_SIGNED_DATA_RECORD, - parsedMinSdkVersion, - signedMinSdkVersion); - } - int signedMaxSdkVersion = signedData.getInt(); - if (signedMaxSdkVersion != parsedMaxSdkVersion) { - result.addError( - Issue.V3_MAX_SDK_VERSION_MISMATCH_BETWEEN_SIGNER_AND_SIGNED_DATA_RECORD, - parsedMaxSdkVersion, - signedMaxSdkVersion); - } - ByteBuffer additionalAttributes = getLengthPrefixedSlice(signedData); - - // Parse the certificates block - int certificateIndex = -1; - while (certificates.hasRemaining()) { - certificateIndex++; - byte[] encodedCert = readLengthPrefixedByteArray(certificates); - X509Certificate certificate; - try { - certificate = X509CertificateUtils.generateCertificate(encodedCert, certFactory); - } catch (CertificateException e) { - result.addError( - Issue.V3_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 because some X509Certificate(Factory) implementations - // re-encode certificates. - certificate = new GuaranteedEncodedFormX509Certificate(certificate, encodedCert); - result.certs.add(certificate); - } - - if (result.certs.isEmpty()) { - result.addError(Issue.V3_SIG_NO_CERTIFICATES); - return; - } - X509Certificate mainCertificate = result.certs.get(0); - byte[] certificatePublicKeyBytes; - try { - certificatePublicKeyBytes = ApkSigningBlockUtils.encodePublicKey(mainCertificate.getPublicKey()); - } catch (InvalidKeyException e) { - System.out.println("Caught an exception encoding the public key: " + e); - e.printStackTrace(); - certificatePublicKeyBytes = mainCertificate.getPublicKey().getEncoded(); - } - if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) { - result.addError( - Issue.V3_SIG_PUBLIC_KEY_MISMATCH_BETWEEN_CERTIFICATE_AND_SIGNATURES_RECORD, - ApkSigningBlockUtils.toHex(certificatePublicKeyBytes), - ApkSigningBlockUtils.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 ApkSigningBlockUtils.Result.SignerInfo.ContentDigest( - sigAlgorithmId, digestBytes)); - } catch (ApkFormatException | BufferUnderflowException e) { - result.addError(Issue.V3_SIG_MALFORMED_DIGEST, digestCount); - return; - } - } - - List sigAlgsFromSignaturesRecord = new ArrayList<>(result.signatures.size()); - for (ApkSigningBlockUtils.Result.SignerInfo.Signature signature : result.signatures) { - sigAlgsFromSignaturesRecord.add(signature.getAlgorithmId()); - } - List sigAlgsFromDigestsRecord = new ArrayList<>(result.contentDigests.size()); - for (ApkSigningBlockUtils.Result.SignerInfo.ContentDigest digest : result.contentDigests) { - sigAlgsFromDigestsRecord.add(digest.getSignatureAlgorithmId()); - } - - if (!sigAlgsFromSignaturesRecord.equals(sigAlgsFromDigestsRecord)) { - result.addError( - Issue.V3_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 = ByteBufferUtils.toByteArray(attribute); - result.additionalAttributes.add( - new ApkSigningBlockUtils.Result.SignerInfo.AdditionalAttribute(id, value)); - if (id == V3SchemeConstants.PROOF_OF_ROTATION_ATTR_ID) { - try { - // SigningCertificateLineage is verified when built - result.signingCertificateLineage = - SigningCertificateLineage.readFromV3AttributeValue(value); - // make sure that the last cert in the chain matches this signer cert - SigningCertificateLineage subLineage = - result.signingCertificateLineage.getSubLineage(result.certs.get(0)); - if (result.signingCertificateLineage.size() != subLineage.size()) { - result.addError(Issue.V3_SIG_POR_CERT_MISMATCH); - } - } catch (SecurityException e) { - result.addError(Issue.V3_SIG_POR_DID_NOT_VERIFY); - } catch (IllegalArgumentException e) { - result.addError(Issue.V3_SIG_POR_CERT_MISMATCH); - } catch (Exception e) { - result.addError(Issue.V3_SIG_MALFORMED_LINEAGE); - } - } else { - result.addWarning(Issue.V3_SIG_UNKNOWN_ADDITIONAL_ATTRIBUTE, id); - } - } catch (ApkFormatException | BufferUnderflowException e) { - result.addError( - Issue.V3_SIG_MALFORMED_ADDITIONAL_ATTRIBUTE, additionalAttributeCount); - return; - } - } - } -} diff --git a/app/src/main/java/com/android/apksig/internal/apk/v3/V3SigningCertificateLineage.java b/app/src/main/java/com/android/apksig/internal/apk/v3/V3SigningCertificateLineage.java deleted file mode 100644 index 4ae7a5365d..0000000000 --- a/app/src/main/java/com/android/apksig/internal/apk/v3/V3SigningCertificateLineage.java +++ /dev/null @@ -1,314 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.apk.v3; - -import static com.android.apksig.internal.apk.ApkSigningBlockUtils.encodeAsLengthPrefixedElement; -import static com.android.apksig.internal.apk.ApkSigningBlockUtils.encodeAsSequenceOfLengthPrefixedElements; -import static com.android.apksig.internal.apk.ApkSigningBlockUtils.getLengthPrefixedSlice; -import static com.android.apksig.internal.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray; - -import com.android.apksig.apk.ApkFormatException; -import com.android.apksig.internal.apk.ApkSigningBlockUtils; -import com.android.apksig.internal.apk.SignatureAlgorithm; -import com.android.apksig.internal.util.GuaranteedEncodedFormX509Certificate; -import com.android.apksig.internal.util.X509CertificateUtils; - -import java.io.IOException; -import java.nio.BufferUnderflowException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -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.X509Certificate; -import java.security.spec.AlgorithmParameterSpec; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Objects; - -/** - * APK Signer Lineage. - * - *

The signer lineage contains a history of signing certificates with each ancestor attesting to - * the validity of its descendant. Each additional descendant represents a new identity that can be - * used to sign an APK, and each generation has accompanying attributes which represent how the - * APK would like to view the older signing certificates, specifically how they should be trusted in - * certain situations. - * - *

Its primary use is to enable APK Signing Certificate Rotation. The Android platform verifies - * the APK Signer Lineage, and if the current signing certificate for the APK is in the Signer - * Lineage, and the Lineage contains the certificate the platform associates with the APK, it will - * allow upgrades to the new certificate. - * - * @see Application Signing - */ -public class V3SigningCertificateLineage { - - private final static int FIRST_VERSION = 1; - private final static int CURRENT_VERSION = FIRST_VERSION; - - /** - * Deserializes the binary representation of an {@link V3SigningCertificateLineage}. Also - * verifies that the structure is well-formed, e.g. that the signature for each node is from its - * parent. - */ - public static List readSigningCertificateLineage(ByteBuffer inputBytes) - throws IOException { - List result = new ArrayList<>(); - int nodeCount = 0; - if (inputBytes == null || !inputBytes.hasRemaining()) { - return null; - } - - ApkSigningBlockUtils.checkByteOrderLittleEndian(inputBytes); - - // FORMAT (little endian): - // * uint32: version code - // * sequence of length-prefixed (uint32): nodes - // * length-prefixed bytes: signed data - // * length-prefixed bytes: certificate - // * uint32: signature algorithm id - // * uint32: flags - // * uint32: signature algorithm id (used by to sign next cert in lineage) - // * length-prefixed bytes: signature over above signed data - - X509Certificate lastCert = null; - int lastSigAlgorithmId = 0; - - try { - int version = inputBytes.getInt(); - if (version != CURRENT_VERSION) { - // we only have one version to worry about right now, so just check it - throw new IllegalArgumentException("Encoded SigningCertificateLineage has a version" - + " different than any of which we are aware"); - } - HashSet certHistorySet = new HashSet<>(); - while (inputBytes.hasRemaining()) { - nodeCount++; - ByteBuffer nodeBytes = getLengthPrefixedSlice(inputBytes); - ByteBuffer signedData = getLengthPrefixedSlice(nodeBytes); - int flags = nodeBytes.getInt(); - int sigAlgorithmId = nodeBytes.getInt(); - SignatureAlgorithm sigAlgorithm = SignatureAlgorithm.findById(lastSigAlgorithmId); - byte[] signature = readLengthPrefixedByteArray(nodeBytes); - - if (lastCert != null) { - // Use previous level cert to verify current level - String jcaSignatureAlgorithm = - sigAlgorithm.getJcaSignatureAlgorithmAndParams().getFirst(); - AlgorithmParameterSpec jcaSignatureAlgorithmParams = - sigAlgorithm.getJcaSignatureAlgorithmAndParams().getSecond(); - PublicKey publicKey = lastCert.getPublicKey(); - Signature sig = Signature.getInstance(jcaSignatureAlgorithm); - sig.initVerify(publicKey); - if (jcaSignatureAlgorithmParams != null) { - sig.setParameter(jcaSignatureAlgorithmParams); - } - sig.update(signedData); - if (!sig.verify(signature)) { - throw new SecurityException("Unable to verify signature of certificate #" - + nodeCount + " using " + jcaSignatureAlgorithm + " when verifying" - + " V3SigningCertificateLineage object"); - } - } - - signedData.rewind(); - byte[] encodedCert = readLengthPrefixedByteArray(signedData); - int signedSigAlgorithm = signedData.getInt(); - if (lastCert != null && lastSigAlgorithmId != signedSigAlgorithm) { - throw new SecurityException("Signing algorithm ID mismatch for certificate #" - + nodeBytes + " when verifying V3SigningCertificateLineage object"); - } - lastCert = X509CertificateUtils.generateCertificate(encodedCert); - lastCert = new GuaranteedEncodedFormX509Certificate(lastCert, encodedCert); - if (certHistorySet.contains(lastCert)) { - throw new SecurityException("Encountered duplicate entries in " - + "SigningCertificateLineage at certificate #" + nodeCount + ". All " - + "signing certificates should be unique"); - } - certHistorySet.add(lastCert); - lastSigAlgorithmId = sigAlgorithmId; - result.add(new SigningCertificateNode( - lastCert, SignatureAlgorithm.findById(signedSigAlgorithm), - SignatureAlgorithm.findById(sigAlgorithmId), signature, flags)); - } - } catch(ApkFormatException | BufferUnderflowException e){ - throw new IOException("Failed to parse V3SigningCertificateLineage object", e); - } catch(NoSuchAlgorithmException | InvalidKeyException - | InvalidAlgorithmParameterException | SignatureException e){ - throw new SecurityException( - "Failed to verify signature over signed data for certificate #" + nodeCount - + " when parsing V3SigningCertificateLineage object", e); - } catch(CertificateException e){ - throw new SecurityException("Failed to decode certificate #" + nodeCount - + " when parsing V3SigningCertificateLineage object", e); - } - return result; - } - - /** - * encode the in-memory representation of this {@code V3SigningCertificateLineage} - */ - public static byte[] encodeSigningCertificateLineage( - List signingCertificateLineage) { - // FORMAT (little endian): - // * version code - // * sequence of length-prefixed (uint32): nodes - // * length-prefixed bytes: signed data - // * length-prefixed bytes: certificate - // * uint32: signature algorithm id - // * uint32: flags - // * uint32: signature algorithm id (used by to sign next cert in lineage) - - List nodes = new ArrayList<>(); - for (SigningCertificateNode node : signingCertificateLineage) { - nodes.add(encodeSigningCertificateNode(node)); - } - byte [] encodedSigningCertificateLineage = encodeAsSequenceOfLengthPrefixedElements(nodes); - - // add the version code (uint32) on top of the encoded nodes - int payloadSize = 4 + encodedSigningCertificateLineage.length; - ByteBuffer encodedWithVersion = ByteBuffer.allocate(payloadSize); - encodedWithVersion.order(ByteOrder.LITTLE_ENDIAN); - encodedWithVersion.putInt(CURRENT_VERSION); - encodedWithVersion.put(encodedSigningCertificateLineage); - return encodedWithVersion.array(); - } - - public static byte[] encodeSigningCertificateNode(SigningCertificateNode node) { - // FORMAT (little endian): - // * length-prefixed bytes: signed data - // * length-prefixed bytes: certificate - // * uint32: signature algorithm id - // * uint32: flags - // * uint32: signature algorithm id (used by to sign next cert in lineage) - // * length-prefixed bytes: signature over signed data - int parentSigAlgorithmId = 0; - if (node.parentSigAlgorithm != null) { - parentSigAlgorithmId = node.parentSigAlgorithm.getId(); - } - int sigAlgorithmId = 0; - if (node.sigAlgorithm != null) { - sigAlgorithmId = node.sigAlgorithm.getId(); - } - byte[] prefixedSignedData = encodeSignedData(node.signingCert, parentSigAlgorithmId); - byte[] prefixedSignature = encodeAsLengthPrefixedElement(node.signature); - int payloadSize = prefixedSignedData.length + 4 + 4 + prefixedSignature.length; - ByteBuffer result = ByteBuffer.allocate(payloadSize); - result.order(ByteOrder.LITTLE_ENDIAN); - result.put(prefixedSignedData); - result.putInt(node.flags); - result.putInt(sigAlgorithmId); - result.put(prefixedSignature); - return result.array(); - } - - public static byte[] encodeSignedData(X509Certificate certificate, int flags) { - try { - byte[] prefixedCertificate = encodeAsLengthPrefixedElement(certificate.getEncoded()); - int payloadSize = 4 + prefixedCertificate.length; - ByteBuffer result = ByteBuffer.allocate(payloadSize); - result.order(ByteOrder.LITTLE_ENDIAN); - result.put(prefixedCertificate); - result.putInt(flags); - return encodeAsLengthPrefixedElement(result.array()); - } catch (CertificateEncodingException e) { - throw new RuntimeException( - "Failed to encode V3SigningCertificateLineage certificate", e); - } - } - - /** - * Represents one signing certificate in the {@link V3SigningCertificateLineage}, which - * generally means it is/was used at some point to sign the same APK of the others in the - * lineage. - */ - public static class SigningCertificateNode { - - public SigningCertificateNode( - X509Certificate signingCert, - SignatureAlgorithm parentSigAlgorithm, - SignatureAlgorithm sigAlgorithm, - byte[] signature, - int flags) { - this.signingCert = signingCert; - this.parentSigAlgorithm = parentSigAlgorithm; - this.sigAlgorithm = sigAlgorithm; - this.signature = signature; - this.flags = flags; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof SigningCertificateNode)) return false; - - SigningCertificateNode that = (SigningCertificateNode) o; - if (!signingCert.equals(that.signingCert)) return false; - if (parentSigAlgorithm != that.parentSigAlgorithm) return false; - if (sigAlgorithm != that.sigAlgorithm) return false; - if (!Arrays.equals(signature, that.signature)) return false; - if (flags != that.flags) return false; - - // we made it - return true; - } - - @Override - public int hashCode() { - int result = Objects.hash(signingCert, parentSigAlgorithm, sigAlgorithm, flags); - result = 31 * result + Arrays.hashCode(signature); - return result; - } - - /** - * the signing cert for this node. This is part of the data signed by the parent node. - */ - public final X509Certificate signingCert; - - /** - * the algorithm used by the this node's parent to bless this data. Its ID value is part of - * the data signed by the parent node. {@code null} for first node. - */ - public final SignatureAlgorithm parentSigAlgorithm; - - /** - * the algorithm used by the this nodeto bless the next node's data. Its ID value is part - * of the signed data of the next node. {@code null} for the last node. - */ - public SignatureAlgorithm sigAlgorithm; - - /** - * signature over the signed data (above). The signature is from this node's parent - * signing certificate, which should correspond to the signing certificate used to sign an - * APK before rotating to this one, and is formed using {@code signatureAlgorithm}. - */ - public final byte[] signature; - - /** - * the flags detailing how the platform should treat this signing cert - */ - public int flags; - } -} diff --git a/app/src/main/java/com/android/apksig/internal/apk/v4/V4SchemeSigner.java b/app/src/main/java/com/android/apksig/internal/apk/v4/V4SchemeSigner.java deleted file mode 100644 index 0a8b7ee43a..0000000000 --- a/app/src/main/java/com/android/apksig/internal/apk/v4/V4SchemeSigner.java +++ /dev/null @@ -1,357 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.apk.v4; - -import static com.android.apksig.internal.apk.ApkSigningBlockUtils.encodeCertificates; -import static com.android.apksig.internal.apk.v2.V2SchemeConstants.APK_SIGNATURE_SCHEME_V2_BLOCK_ID; -import static com.android.apksig.internal.apk.v3.V3SchemeConstants.APK_SIGNATURE_SCHEME_V3_BLOCK_ID; - -import com.android.apksig.apk.ApkUtils; -import com.android.apksig.internal.apk.ApkSigningBlockUtils; -import com.android.apksig.internal.apk.ApkSigningBlockUtils.SignerConfig; -import com.android.apksig.internal.apk.ContentDigestAlgorithm; -import com.android.apksig.internal.apk.SignatureAlgorithm; -import com.android.apksig.internal.apk.SignatureInfo; -import com.android.apksig.internal.apk.v2.V2SchemeVerifier; -import com.android.apksig.internal.apk.v3.V3SchemeSigner; -import com.android.apksig.internal.apk.v3.V3SchemeVerifier; -import com.android.apksig.internal.util.Pair; -import com.android.apksig.util.DataSource; -import com.android.apksig.zip.ZipFormatException; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; -import java.security.SignatureException; -import java.security.cert.CertificateEncodingException; -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Set; - -/** - * APK Signature Scheme V4 signer. V4 scheme file contains 2 mandatory fields - used during - * installation. And optional verity tree - has to be present during session commit. - *

- * The fields: - *

- * 1. hashingInfo - verity root hash and hashing info, - * 2. signingInfo - certificate, public key and signature, - * For more details see V4Signature. - *

- * (optional) verityTree: integer size prepended bytes of the verity hash tree. - *

- * TODO(schfan): Add v4 unit tests - */ -public abstract class V4SchemeSigner { - /** - * Hidden constructor to prevent instantiation. - */ - private V4SchemeSigner() { - } - - /** - * Based on a public key, return a signing algorithm that supports verity. - */ - public static List getSuggestedSignatureAlgorithms(PublicKey signingKey, - int minSdkVersion, boolean apkSigningBlockPaddingSupported, - boolean deterministicDsaSigning) - throws InvalidKeyException { - List algorithms = V3SchemeSigner.getSuggestedSignatureAlgorithms( - signingKey, minSdkVersion, - apkSigningBlockPaddingSupported, deterministicDsaSigning); - // Keeping only supported algorithms. - for (Iterator iter = algorithms.listIterator(); iter.hasNext(); ) { - final SignatureAlgorithm algorithm = iter.next(); - if (!isSupported(algorithm.getContentDigestAlgorithm(), false)) { - iter.remove(); - } - } - return algorithms; - } - - /** - * Compute hash tree and generate v4 signature for a given APK. Write the serialized data to - * output file. - */ - public static void generateV4Signature( - DataSource apkContent, SignerConfig signerConfig, File outputFile) - throws IOException, InvalidKeyException, NoSuchAlgorithmException { - Pair pair = generateV4Signature(apkContent, signerConfig); - try (final OutputStream output = new FileOutputStream(outputFile)) { - pair.getFirst().writeTo(output); - V4Signature.writeBytes(output, pair.getSecond()); - } catch (IOException e) { - outputFile.delete(); - throw e; - } - } - - /** Generate v4 signature and hash tree for a given APK. */ - public static Pair generateV4Signature( - DataSource apkContent, - SignerConfig signerConfig) - throws IOException, InvalidKeyException, NoSuchAlgorithmException { - // Salt has to stay empty for fs-verity compatibility. - final byte[] salt = null; - // Not used by apksigner. - final byte[] additionalData = null; - - final long fileSize = apkContent.size(); - - // Obtaining first supported digest from v2/v3 blocks (SHA256 or SHA512). - final byte[] apkDigest = getApkDigest(apkContent); - - // Obtaining the merkle tree and the root hash in verity format. - ApkSigningBlockUtils.VerityTreeAndDigest verityContentDigestInfo = - ApkSigningBlockUtils.computeChunkVerityTreeAndDigest(apkContent); - - final ContentDigestAlgorithm verityContentDigestAlgorithm = - verityContentDigestInfo.contentDigestAlgorithm; - final byte[] rootHash = verityContentDigestInfo.rootHash; - final byte[] tree = verityContentDigestInfo.tree; - - final Pair hashingAlgorithmBlockSizePair = convertToV4HashingInfo( - verityContentDigestAlgorithm); - final V4Signature.HashingInfo hashingInfo = new V4Signature.HashingInfo( - hashingAlgorithmBlockSizePair.getFirst(), hashingAlgorithmBlockSizePair.getSecond(), - salt, rootHash); - - // Generating SigningInfo and combining everything into V4Signature. - final V4Signature signature; - try { - signature = generateSignature(signerConfig, hashingInfo, apkDigest, additionalData, - fileSize); - } catch (InvalidKeyException | SignatureException | CertificateEncodingException e) { - throw new InvalidKeyException("Signer failed", e); - } - - return Pair.of(signature, tree); - } - - private static V4Signature generateSignature( - SignerConfig signerConfig, - V4Signature.HashingInfo hashingInfo, - byte[] apkDigest, byte[] additionaData, long fileSize) - throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, - CertificateEncodingException { - if (signerConfig.certificates.isEmpty()) { - throw new SignatureException("No certificates configured for signer"); - } - if (signerConfig.certificates.size() != 1) { - throw new CertificateEncodingException("Should only have one certificate"); - } - - // Collecting data for signing. - final PublicKey publicKey = signerConfig.certificates.get(0).getPublicKey(); - - final List encodedCertificates = encodeCertificates(signerConfig.certificates); - final byte[] encodedCertificate = encodedCertificates.get(0); - - final V4Signature.SigningInfo signingInfoNoSignature = new V4Signature.SigningInfo(apkDigest, - encodedCertificate, additionaData, publicKey.getEncoded(), -1, null); - - final byte[] data = V4Signature.getSigningData(fileSize, hashingInfo, - signingInfoNoSignature); - - // Signing. - final List> signatures = - ApkSigningBlockUtils.generateSignaturesOverData(signerConfig, data); - if (signatures.size() != 1) { - throw new SignatureException("Should only be one signature generated"); - } - - final int signatureAlgorithmId = signatures.get(0).getFirst(); - final byte[] signature = signatures.get(0).getSecond(); - - final V4Signature.SigningInfo signingInfo = new V4Signature.SigningInfo(apkDigest, - encodedCertificate, additionaData, publicKey.getEncoded(), signatureAlgorithmId, - signature); - - return new V4Signature(V4Signature.CURRENT_VERSION, hashingInfo.toByteArray(), - signingInfo.toByteArray()); - } - - // Get digest by parsing the V2/V3-signed apk and choosing the first digest of supported type. - private static byte[] getApkDigest(DataSource apk) throws IOException { - ApkUtils.ZipSections zipSections; - try { - zipSections = ApkUtils.findZipSections(apk); - } catch (ZipFormatException e) { - throw new IOException("Malformed APK: not a ZIP archive", e); - } - - final SignatureException v3Exception; - try { - return getBestV3Digest(apk, zipSections); - } catch (SignatureException e) { - v3Exception = e; - } - - final SignatureException v2Exception; - try { - return getBestV2Digest(apk, zipSections); - } catch (SignatureException e) { - v2Exception = e; - } - - throw new IOException( - "Failed to obtain v2/v3 digest, v3 exception: " + v3Exception + ", v2 exception: " - + v2Exception); - } - - private static byte[] getBestV3Digest(DataSource apk, ApkUtils.ZipSections zipSections) - throws SignatureException { - final Set contentDigestsToVerify = new HashSet<>(1); - final ApkSigningBlockUtils.Result result = new ApkSigningBlockUtils.Result( - ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V3); - try { - final SignatureInfo signatureInfo = - ApkSigningBlockUtils.findSignature(apk, zipSections, - APK_SIGNATURE_SCHEME_V3_BLOCK_ID, result); - final ByteBuffer apkSignatureSchemeV3Block = signatureInfo.signatureBlock; - V3SchemeVerifier.parseSigners(apkSignatureSchemeV3Block, contentDigestsToVerify, - result); - } catch (Exception e) { - throw new SignatureException("Failed to extract and parse v3 block", e); - } - - if (result.signers.size() != 1) { - throw new SignatureException("Should only have one signer, errors: " + result.getErrors()); - } - - ApkSigningBlockUtils.Result.SignerInfo signer = result.signers.get(0); - if (signer.containsErrors()) { - throw new SignatureException("Parsing failed: " + signer.getErrors()); - } - - final List contentDigests = - result.signers.get(0).contentDigests; - return pickBestDigest(contentDigests); - } - - private static byte[] getBestV2Digest(DataSource apk, ApkUtils.ZipSections zipSections) - throws SignatureException { - final Set contentDigestsToVerify = new HashSet<>(1); - final Set foundApkSigSchemeIds = new HashSet<>(1); - final ApkSigningBlockUtils.Result result = new ApkSigningBlockUtils.Result( - ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V2); - try { - final SignatureInfo signatureInfo = - ApkSigningBlockUtils.findSignature(apk, zipSections, - APK_SIGNATURE_SCHEME_V2_BLOCK_ID, result); - final ByteBuffer apkSignatureSchemeV2Block = signatureInfo.signatureBlock; - V2SchemeVerifier.parseSigners( - apkSignatureSchemeV2Block, - contentDigestsToVerify, - Collections.emptyMap(), - foundApkSigSchemeIds, - Integer.MAX_VALUE, - Integer.MAX_VALUE, - result); - } catch (Exception e) { - throw new SignatureException("Failed to extract and parse v2 block", e); - } - - if (result.signers.size() != 1) { - throw new SignatureException("Should only have one signer, errors: " + result.getErrors()); - } - - ApkSigningBlockUtils.Result.SignerInfo signer = result.signers.get(0); - if (signer.containsErrors()) { - throw new SignatureException("Parsing failed: " + signer.getErrors()); - } - - final List contentDigests = - signer.contentDigests; - return pickBestDigest(contentDigests); - } - - private static byte[] pickBestDigest(List contentDigests) throws SignatureException { - if (contentDigests == null || contentDigests.isEmpty()) { - throw new SignatureException("Should have at least one digest"); - } - - int bestAlgorithmOrder = -1; - byte[] bestDigest = null; - for (ApkSigningBlockUtils.Result.SignerInfo.ContentDigest contentDigest : contentDigests) { - final SignatureAlgorithm signatureAlgorithm = - SignatureAlgorithm.findById(contentDigest.getSignatureAlgorithmId()); - final ContentDigestAlgorithm contentDigestAlgorithm = - signatureAlgorithm.getContentDigestAlgorithm(); - if (!isSupported(contentDigestAlgorithm, true)) { - continue; - } - final int algorithmOrder = digestAlgorithmSortingOrder(contentDigestAlgorithm); - if (bestAlgorithmOrder < algorithmOrder) { - bestAlgorithmOrder = algorithmOrder; - bestDigest = contentDigest.getValue(); - } - } - if (bestDigest == null) { - throw new SignatureException("Failed to find a supported digest in the source APK"); - } - return bestDigest; - } - - // Use the same order as in the ApkSignatureSchemeV3Verifier to make sure the digest - // verification in framework works. - public static int digestAlgorithmSortingOrder(ContentDigestAlgorithm contentDigestAlgorithm) { - switch (contentDigestAlgorithm) { - case CHUNKED_SHA256: - return 0; - case VERITY_CHUNKED_SHA256: - return 1; - case CHUNKED_SHA512: - return 2; - default: - return -1; - } - } - - private static boolean isSupported(final ContentDigestAlgorithm contentDigestAlgorithm, - boolean forV3Digest) { - if (contentDigestAlgorithm == null) { - return false; - } - if (contentDigestAlgorithm == ContentDigestAlgorithm.CHUNKED_SHA256 - || contentDigestAlgorithm == ContentDigestAlgorithm.CHUNKED_SHA512 - || (forV3Digest - && contentDigestAlgorithm == ContentDigestAlgorithm.VERITY_CHUNKED_SHA256)) { - return true; - } - return false; - } - - private static Pair convertToV4HashingInfo(ContentDigestAlgorithm algorithm) - throws NoSuchAlgorithmException { - switch (algorithm) { - case VERITY_CHUNKED_SHA256: - return Pair.of(V4Signature.HASHING_ALGORITHM_SHA256, - V4Signature.LOG2_BLOCK_SIZE_4096_BYTES); - default: - throw new NoSuchAlgorithmException( - "Invalid hash algorithm, only SHA2-256 over 4 KB chunks supported."); - } - } -} diff --git a/app/src/main/java/com/android/apksig/internal/apk/v4/V4SchemeVerifier.java b/app/src/main/java/com/android/apksig/internal/apk/v4/V4SchemeVerifier.java deleted file mode 100644 index 0a8484b342..0000000000 --- a/app/src/main/java/com/android/apksig/internal/apk/v4/V4SchemeVerifier.java +++ /dev/null @@ -1,250 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.apk.v4; - -import static com.android.apksig.internal.apk.ApkSigningBlockUtils.toHex; - -import com.android.apksig.ApkVerifier; -import com.android.apksig.ApkVerifier.Issue; -import com.android.apksig.internal.apk.ApkSigningBlockUtils; -import com.android.apksig.internal.apk.ContentDigestAlgorithm; -import com.android.apksig.internal.apk.SignatureAlgorithm; -import com.android.apksig.internal.util.GuaranteedEncodedFormX509Certificate; -import com.android.apksig.internal.util.X509CertificateUtils; -import com.android.apksig.util.DataSource; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -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.CertificateException; -import java.security.cert.X509Certificate; -import java.security.spec.AlgorithmParameterSpec; -import java.security.spec.X509EncodedKeySpec; -import java.util.Arrays; - -/** - * APK Signature Scheme V4 verifier. - *

- * Verifies the serialized V4Signature file against an APK. - */ -public abstract class V4SchemeVerifier { - /** - * Hidden constructor to prevent instantiation. - */ - private V4SchemeVerifier() { - } - - /** - *

- * The main goals of the verifier are: 1) parse V4Signature file fields 2) verifies the PKCS7 - * signature block against the raw root hash bytes in the proto field 3) verifies that the raw - * root hash matches with the actual hash tree root of the give APK 4) if the file contains a - * verity tree, verifies that it matches with the actual verity tree computed from the given - * APK. - *

- */ - public static ApkSigningBlockUtils.Result verify(DataSource apk, File v4SignatureFile) - throws IOException, NoSuchAlgorithmException { - final V4Signature signature; - final byte[] tree; - try (InputStream input = new FileInputStream(v4SignatureFile)) { - signature = V4Signature.readFrom(input); - tree = V4Signature.readBytes(input); - } - - final ApkSigningBlockUtils.Result result = new ApkSigningBlockUtils.Result( - ApkSigningBlockUtils.VERSION_APK_SIGNATURE_SCHEME_V4); - - if (signature == null) { - result.addError(Issue.V4_SIG_NO_SIGNATURES, - "Signature file does not contain a v4 signature."); - return result; - } - - if (signature.version != V4Signature.CURRENT_VERSION) { - result.addWarning(Issue.V4_SIG_VERSION_NOT_CURRENT, signature.version, - V4Signature.CURRENT_VERSION); - } - - V4Signature.HashingInfo hashingInfo = V4Signature.HashingInfo.fromByteArray( - signature.hashingInfo); - V4Signature.SigningInfo signingInfo = V4Signature.SigningInfo.fromByteArray( - signature.signingInfo); - - final byte[] signedData = V4Signature.getSigningData(apk.size(), hashingInfo, signingInfo); - - // First, verify the signature over signedData. - ApkSigningBlockUtils.Result.SignerInfo signerInfo = parseAndVerifySignatureBlock( - signingInfo, signedData); - result.signers.add(signerInfo); - if (result.containsErrors()) { - return result; - } - - // Second, check if the root hash and the tree are correct. - verifyRootHashAndTree(apk, signerInfo, hashingInfo.rawRootHash, tree); - if (!result.containsErrors()) { - result.verified = true; - } - - return result; - } - - /** - * Parses the provided signature block and populates the {@code result}. - *

- * This verifies {@signingInfo} over {@code signedData}, as well as parsing the certificate - * contained in the signature block. This method adds one or more errors to the {@code result}. - */ - private static ApkSigningBlockUtils.Result.SignerInfo parseAndVerifySignatureBlock( - V4Signature.SigningInfo signingInfo, - final byte[] signedData) throws NoSuchAlgorithmException { - final ApkSigningBlockUtils.Result.SignerInfo result = - new ApkSigningBlockUtils.Result.SignerInfo(); - result.index = 0; - - final int sigAlgorithmId = signingInfo.signatureAlgorithmId; - final byte[] sigBytes = signingInfo.signature; - result.signatures.add( - new ApkSigningBlockUtils.Result.SignerInfo.Signature(sigAlgorithmId, sigBytes)); - - SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.findById(sigAlgorithmId); - if (signatureAlgorithm == null) { - result.addError(Issue.V4_SIG_UNKNOWN_SIG_ALGORITHM, sigAlgorithmId); - return result; - } - - String jcaSignatureAlgorithm = - signatureAlgorithm.getJcaSignatureAlgorithmAndParams().getFirst(); - AlgorithmParameterSpec jcaSignatureAlgorithmParams = - signatureAlgorithm.getJcaSignatureAlgorithmAndParams().getSecond(); - - String keyAlgorithm = signatureAlgorithm.getJcaKeyAlgorithm(); - - final byte[] publicKeyBytes = signingInfo.publicKey; - PublicKey publicKey; - try { - publicKey = KeyFactory.getInstance(keyAlgorithm).generatePublic( - new X509EncodedKeySpec(publicKeyBytes)); - } catch (Exception e) { - result.addError(Issue.V4_SIG_MALFORMED_PUBLIC_KEY, e); - return result; - } - - try { - Signature sig = Signature.getInstance(jcaSignatureAlgorithm); - sig.initVerify(publicKey); - if (jcaSignatureAlgorithmParams != null) { - sig.setParameter(jcaSignatureAlgorithmParams); - } - sig.update(signedData); - if (!sig.verify(sigBytes)) { - result.addError(Issue.V4_SIG_DID_NOT_VERIFY, signatureAlgorithm); - return result; - } - result.verifiedSignatures.put(signatureAlgorithm, sigBytes); - } catch (InvalidKeyException | InvalidAlgorithmParameterException - | SignatureException e) { - result.addError(Issue.V4_SIG_VERIFY_EXCEPTION, signatureAlgorithm, e); - return result; - } - - if (signingInfo.certificate == null) { - result.addError(Issue.V4_SIG_NO_CERTIFICATE); - return result; - } - - final X509Certificate certificate; - try { - // Wrap the cert so that the result's getEncoded returns exactly the original encoded - // form. Without this, getEncoded may return a different form from what was stored in - // the signature. This is because some X509Certificate(Factory) implementations - // re-encode certificates. - certificate = new GuaranteedEncodedFormX509Certificate( - X509CertificateUtils.generateCertificate(signingInfo.certificate), - signingInfo.certificate); - } catch (CertificateException e) { - result.addError(Issue.V4_SIG_MALFORMED_CERTIFICATE, e); - return result; - } - result.certs.add(certificate); - - byte[] certificatePublicKeyBytes; - try { - certificatePublicKeyBytes = ApkSigningBlockUtils.encodePublicKey( - certificate.getPublicKey()); - } catch (InvalidKeyException e) { - System.out.println("Caught an exception encoding the public key: " + e); - e.printStackTrace(); - certificatePublicKeyBytes = certificate.getPublicKey().getEncoded(); - } - if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) { - result.addError( - Issue.V4_SIG_PUBLIC_KEY_MISMATCH_BETWEEN_CERTIFICATE_AND_SIGNATURES_RECORD, - ApkSigningBlockUtils.toHex(certificatePublicKeyBytes), - ApkSigningBlockUtils.toHex(publicKeyBytes)); - return result; - } - - // Add apk digest from the file to the result. - ApkSigningBlockUtils.Result.SignerInfo.ContentDigest contentDigest = - new ApkSigningBlockUtils.Result.SignerInfo.ContentDigest( - 0 /* signature algorithm id doesn't matter here */, signingInfo.apkDigest); - result.contentDigests.add(contentDigest); - - return result; - } - - private static void verifyRootHashAndTree(DataSource apkContent, - ApkSigningBlockUtils.Result.SignerInfo signerInfo, byte[] expectedDigest, - byte[] expectedTree) throws IOException, NoSuchAlgorithmException { - ApkSigningBlockUtils.VerityTreeAndDigest actualContentDigestInfo = - ApkSigningBlockUtils.computeChunkVerityTreeAndDigest(apkContent); - - ContentDigestAlgorithm algorithm = actualContentDigestInfo.contentDigestAlgorithm; - final byte[] actualDigest = actualContentDigestInfo.rootHash; - final byte[] actualTree = actualContentDigestInfo.tree; - - if (!Arrays.equals(expectedDigest, actualDigest)) { - signerInfo.addError( - ApkVerifier.Issue.V4_SIG_APK_ROOT_DID_NOT_VERIFY, - algorithm, - toHex(expectedDigest), - toHex(actualDigest)); - return; - } - // Only check verity tree if it is not empty - if (expectedTree != null && !Arrays.equals(expectedTree, actualTree)) { - signerInfo.addError( - ApkVerifier.Issue.V4_SIG_APK_TREE_DID_NOT_VERIFY, - algorithm, - toHex(expectedDigest), - toHex(actualDigest)); - return; - } - - signerInfo.verifiedContentDigests.put(algorithm, actualDigest); - } -} diff --git a/app/src/main/java/com/android/apksig/internal/apk/v4/V4Signature.java b/app/src/main/java/com/android/apksig/internal/apk/v4/V4Signature.java deleted file mode 100644 index e36ed605a0..0000000000 --- a/app/src/main/java/com/android/apksig/internal/apk/v4/V4Signature.java +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.apk.v4; - -import java.io.EOFException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; - -public class V4Signature { - public static final int CURRENT_VERSION = 2; - - public static final int HASHING_ALGORITHM_SHA256 = 1; - public static final byte LOG2_BLOCK_SIZE_4096_BYTES = 12; - - public static class HashingInfo { - public final int hashAlgorithm; // only 1 == SHA256 supported - public final byte log2BlockSize; // only 12 (block size 4096) supported now - public final byte[] salt; // used exactly as in fs-verity, 32 bytes max - public final byte[] rawRootHash; // salted digest of the first Merkle tree page - - HashingInfo(int hashAlgorithm, byte log2BlockSize, byte[] salt, byte[] rawRootHash) { - this.hashAlgorithm = hashAlgorithm; - this.log2BlockSize = log2BlockSize; - this.salt = salt; - this.rawRootHash = rawRootHash; - } - - static HashingInfo fromByteArray(byte[] bytes) throws IOException { - ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN); - final int hashAlgorithm = buffer.getInt(); - final byte log2BlockSize = buffer.get(); - byte[] salt = readBytes(buffer); - byte[] rawRootHash = readBytes(buffer); - return new HashingInfo(hashAlgorithm, log2BlockSize, salt, rawRootHash); - } - - byte[] toByteArray() { - final int size = 4/*hashAlgorithm*/ + 1/*log2BlockSize*/ + bytesSize(this.salt) - + bytesSize(this.rawRootHash); - ByteBuffer buffer = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN); - buffer.putInt(this.hashAlgorithm); - buffer.put(this.log2BlockSize); - writeBytes(buffer, this.salt); - writeBytes(buffer, this.rawRootHash); - return buffer.array(); - } - } - - public static class SigningInfo { - public final byte[] apkDigest; // used to match with the corresponding APK - public final byte[] certificate; // ASN.1 DER form - public final byte[] additionalData; // a free-form binary data blob - public final byte[] publicKey; // ASN.1 DER, must match the certificate - public final int signatureAlgorithmId; // see the APK v2 doc for the list - public final byte[] signature; - - SigningInfo(byte[] apkDigest, byte[] certificate, byte[] additionalData, - byte[] publicKey, int signatureAlgorithmId, byte[] signature) { - this.apkDigest = apkDigest; - this.certificate = certificate; - this.additionalData = additionalData; - this.publicKey = publicKey; - this.signatureAlgorithmId = signatureAlgorithmId; - this.signature = signature; - } - - static SigningInfo fromByteArray(byte[] bytes) throws IOException { - ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN); - byte[] apkDigest = readBytes(buffer); - byte[] certificate = readBytes(buffer); - byte[] additionalData = readBytes(buffer); - byte[] publicKey = readBytes(buffer); - int signatureAlgorithmId = buffer.getInt(); - byte[] signature = readBytes(buffer); - return new SigningInfo(apkDigest, certificate, additionalData, publicKey, - signatureAlgorithmId, signature); - } - - byte[] toByteArray() { - final int size = bytesSize(this.apkDigest) + bytesSize(this.certificate) + bytesSize( - this.additionalData) + bytesSize(this.publicKey) + 4/*signatureAlgorithmId*/ - + bytesSize(this.signature); - ByteBuffer buffer = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN); - writeBytes(buffer, this.apkDigest); - writeBytes(buffer, this.certificate); - writeBytes(buffer, this.additionalData); - writeBytes(buffer, this.publicKey); - buffer.putInt(this.signatureAlgorithmId); - writeBytes(buffer, this.signature); - return buffer.array(); - } - } - - public final int version; // Always 2 for now. - public final byte[] hashingInfo; - public final byte[] signingInfo; // Passed as-is to the kernel. Can be retrieved later. - - V4Signature(int version, byte[] hashingInfo, byte[] signingInfo) { - this.version = version; - this.hashingInfo = hashingInfo; - this.signingInfo = signingInfo; - } - - static V4Signature readFrom(InputStream stream) throws IOException { - final int version = readIntLE(stream); - if (version != CURRENT_VERSION) { - throw new IOException("Invalid signature version."); - } - final byte[] hashingInfo = readBytes(stream); - final byte[] signingInfo = readBytes(stream); - return new V4Signature(version, hashingInfo, signingInfo); - } - - public void writeTo(OutputStream stream) throws IOException { - writeIntLE(stream, this.version); - writeBytes(stream, this.hashingInfo); - writeBytes(stream, this.signingInfo); - } - - static byte[] getSigningData(long fileSize, HashingInfo hashingInfo, SigningInfo signingInfo) { - final int size = - 4/*size*/ + 8/*fileSize*/ + 4/*hash_algorithm*/ + 1/*log2_blocksize*/ + bytesSize( - hashingInfo.salt) + bytesSize(hashingInfo.rawRootHash) + bytesSize( - signingInfo.apkDigest) + bytesSize(signingInfo.certificate) + bytesSize( - signingInfo.additionalData); - ByteBuffer buffer = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN); - buffer.putInt(size); - buffer.putLong(fileSize); - buffer.putInt(hashingInfo.hashAlgorithm); - buffer.put(hashingInfo.log2BlockSize); - writeBytes(buffer, hashingInfo.salt); - writeBytes(buffer, hashingInfo.rawRootHash); - writeBytes(buffer, signingInfo.apkDigest); - writeBytes(buffer, signingInfo.certificate); - writeBytes(buffer, signingInfo.additionalData); - return buffer.array(); - } - - // Utility methods. - static int bytesSize(byte[] bytes) { - return 4/*length*/ + (bytes == null ? 0 : bytes.length); - } - - static void readFully(InputStream stream, byte[] buffer) throws IOException { - int len = buffer.length; - int n = 0; - while (n < len) { - int count = stream.read(buffer, n, len - n); - if (count < 0) { - throw new EOFException(); - } - n += count; - } - } - - static int readIntLE(InputStream stream) throws IOException { - final byte[] buffer = new byte[4]; - readFully(stream, buffer); - return ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).getInt(); - } - - static void writeIntLE(OutputStream stream, int v) throws IOException { - final byte[] buffer = ByteBuffer.wrap(new byte[4]).order(ByteOrder.LITTLE_ENDIAN).putInt(v).array(); - stream.write(buffer); - } - - static byte[] readBytes(InputStream stream) throws IOException { - try { - final int size = readIntLE(stream); - final byte[] bytes = new byte[size]; - readFully(stream, bytes); - return bytes; - } catch (EOFException ignored) { - return null; - } - } - - static byte[] readBytes(ByteBuffer buffer) throws IOException { - if (buffer.remaining() < 4) { - throw new EOFException(); - } - final int size = buffer.getInt(); - if (buffer.remaining() < size) { - throw new EOFException(); - } - final byte[] bytes = new byte[size]; - buffer.get(bytes); - return bytes; - } - - static void writeBytes(OutputStream stream, byte[] bytes) throws IOException { - if (bytes == null) { - writeIntLE(stream, 0); - return; - } - writeIntLE(stream, bytes.length); - stream.write(bytes); - } - - static void writeBytes(ByteBuffer buffer, byte[] bytes) { - if (bytes == null) { - buffer.putInt(0); - return; - } - buffer.putInt(bytes.length); - buffer.put(bytes); - } -} diff --git a/app/src/main/java/com/android/apksig/internal/asn1/Asn1BerParser.java b/app/src/main/java/com/android/apksig/internal/asn1/Asn1BerParser.java deleted file mode 100644 index 160dc4e233..0000000000 --- a/app/src/main/java/com/android/apksig/internal/asn1/Asn1BerParser.java +++ /dev/null @@ -1,673 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.asn1; - -import com.android.apksig.internal.asn1.ber.BerDataValue; -import com.android.apksig.internal.asn1.ber.BerDataValueFormatException; -import com.android.apksig.internal.asn1.ber.BerDataValueReader; -import com.android.apksig.internal.asn1.ber.BerEncoding; -import com.android.apksig.internal.asn1.ber.ByteBufferBerDataValueReader; -import com.android.apksig.internal.util.ByteBufferUtils; - -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.math.BigInteger; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Parser of ASN.1 BER-encoded structures. - * - *

Structure is described to the parser by providing a class annotated with {@link Asn1Class}, - * containing fields annotated with {@link Asn1Field}. - */ -public final class Asn1BerParser { - private Asn1BerParser() {} - - /** - * Returns the ASN.1 structure contained in the BER encoded input. - * - * @param encoded encoded input. If the decoding operation succeeds, the position of this buffer - * is advanced to the first position following the end of the consumed structure. - * @param containerClass class describing the structure of the input. The class must meet the - * following requirements: - *

    - *
  • The class must be annotated with {@link Asn1Class}.
  • - *
  • The class must expose a public no-arg constructor.
  • - *
  • Member fields of the class which are populated with parsed input must be - * annotated with {@link Asn1Field} and be public and non-final.
  • - *
- * - * @throws Asn1DecodingException if the input could not be decoded into the specified Java - * object - */ - public static T parse(ByteBuffer encoded, Class containerClass) - throws Asn1DecodingException { - BerDataValue containerDataValue; - try { - containerDataValue = new ByteBufferBerDataValueReader(encoded).readDataValue(); - } catch (BerDataValueFormatException e) { - throw new Asn1DecodingException("Failed to decode top-level data value", e); - } - if (containerDataValue == null) { - throw new Asn1DecodingException("Empty input"); - } - return parse(containerDataValue, containerClass); - } - - /** - * Returns the implicit {@code SET OF} contained in the provided ASN.1 BER input. Implicit means - * that this method does not care whether the tag number of this data structure is - * {@code SET OF} and whether the tag class is {@code UNIVERSAL}. - * - *

Note: The returned type is {@link List} rather than {@link java.util.Set} because ASN.1 - * SET may contain duplicate elements. - * - * @param encoded encoded input. If the decoding operation succeeds, the position of this buffer - * is advanced to the first position following the end of the consumed structure. - * @param elementClass class describing the structure of the values/elements contained in this - * container. The class must meet the following requirements: - *

    - *
  • The class must be annotated with {@link Asn1Class}.
  • - *
  • The class must expose a public no-arg constructor.
  • - *
  • Member fields of the class which are populated with parsed input must be - * annotated with {@link Asn1Field} and be public and non-final.
  • - *
- * - * @throws Asn1DecodingException if the input could not be decoded into the specified Java - * object - */ - public static List parseImplicitSetOf(ByteBuffer encoded, Class elementClass) - throws Asn1DecodingException { - BerDataValue containerDataValue; - try { - containerDataValue = new ByteBufferBerDataValueReader(encoded).readDataValue(); - } catch (BerDataValueFormatException e) { - throw new Asn1DecodingException("Failed to decode top-level data value", e); - } - if (containerDataValue == null) { - throw new Asn1DecodingException("Empty input"); - } - return parseSetOf(containerDataValue, elementClass); - } - - private static T parse(BerDataValue container, Class containerClass) - throws Asn1DecodingException { - if (container == null) { - throw new NullPointerException("container == null"); - } - if (containerClass == null) { - throw new NullPointerException("containerClass == null"); - } - - Asn1Type dataType = getContainerAsn1Type(containerClass); - switch (dataType) { - case CHOICE: - return parseChoice(container, containerClass); - - case SEQUENCE: - { - int expectedTagClass = BerEncoding.TAG_CLASS_UNIVERSAL; - int expectedTagNumber = BerEncoding.getTagNumber(dataType); - if ((container.getTagClass() != expectedTagClass) - || (container.getTagNumber() != expectedTagNumber)) { - throw new Asn1UnexpectedTagException( - "Unexpected data value read as " + containerClass.getName() - + ". Expected " + BerEncoding.tagClassAndNumberToString( - expectedTagClass, expectedTagNumber) - + ", but read: " + BerEncoding.tagClassAndNumberToString( - container.getTagClass(), container.getTagNumber())); - } - return parseSequence(container, containerClass); - } - case UNENCODED_CONTAINER: - return parseSequence(container, containerClass, true); - default: - throw new Asn1DecodingException("Parsing container " + dataType + " not supported"); - } - } - - private static T parseChoice(BerDataValue dataValue, Class containerClass) - throws Asn1DecodingException { - List fields = getAnnotatedFields(containerClass); - if (fields.isEmpty()) { - throw new Asn1DecodingException( - "No fields annotated with " + Asn1Field.class.getName() - + " in CHOICE class " + containerClass.getName()); - } - - // Check that class + tagNumber don't clash between the choices - for (int i = 0; i < fields.size() - 1; i++) { - AnnotatedField f1 = fields.get(i); - int tagNumber1 = f1.getBerTagNumber(); - int tagClass1 = f1.getBerTagClass(); - for (int j = i + 1; j < fields.size(); j++) { - AnnotatedField f2 = fields.get(j); - int tagNumber2 = f2.getBerTagNumber(); - int tagClass2 = f2.getBerTagClass(); - if ((tagNumber1 == tagNumber2) && (tagClass1 == tagClass2)) { - throw new Asn1DecodingException( - "CHOICE fields are indistinguishable because they have the same tag" - + " class and number: " + containerClass.getName() - + "." + f1.getField().getName() - + " and ." + f2.getField().getName()); - } - } - } - - // Instantiate the container object / result - T obj; - try { - obj = containerClass.getConstructor().newInstance(); - } catch (IllegalArgumentException | ReflectiveOperationException e) { - throw new Asn1DecodingException("Failed to instantiate " + containerClass.getName(), e); - } - // Set the matching field's value from the data value - for (AnnotatedField field : fields) { - try { - field.setValueFrom(dataValue, obj); - return obj; - } catch (Asn1UnexpectedTagException expected) { - // not a match - } - } - - throw new Asn1DecodingException( - "No options of CHOICE " + containerClass.getName() + " matched"); - } - - private static T parseSequence(BerDataValue container, Class containerClass) - throws Asn1DecodingException { - return parseSequence(container, containerClass, false); - } - - private static T parseSequence(BerDataValue container, Class containerClass, - boolean isUnencodedContainer) throws Asn1DecodingException { - List fields = getAnnotatedFields(containerClass); - Collections.sort( - fields, (f1, f2) -> f1.getAnnotation().index() - f2.getAnnotation().index()); - // Check that there are no fields with the same index - if (fields.size() > 1) { - AnnotatedField lastField = null; - for (AnnotatedField field : fields) { - if ((lastField != null) - && (lastField.getAnnotation().index() == field.getAnnotation().index())) { - throw new Asn1DecodingException( - "Fields have the same index: " + containerClass.getName() - + "." + lastField.getField().getName() - + " and ." + field.getField().getName()); - } - lastField = field; - } - } - - // Instantiate the container object / result - T t; - try { - t = containerClass.getConstructor().newInstance(); - } catch (IllegalArgumentException | ReflectiveOperationException e) { - throw new Asn1DecodingException("Failed to instantiate " + containerClass.getName(), e); - } - - // Parse fields one by one. A complication is that there may be optional fields. - int nextUnreadFieldIndex = 0; - BerDataValueReader elementsReader = container.contentsReader(); - while (nextUnreadFieldIndex < fields.size()) { - BerDataValue dataValue; - try { - // if this is the first field of an unencoded container then the entire contents of - // the container should be used when assigning to this field. - if (isUnencodedContainer && nextUnreadFieldIndex == 0) { - dataValue = container; - } else { - dataValue = elementsReader.readDataValue(); - } - } catch (BerDataValueFormatException e) { - throw new Asn1DecodingException("Malformed data value", e); - } - if (dataValue == null) { - break; - } - - for (int i = nextUnreadFieldIndex; i < fields.size(); i++) { - AnnotatedField field = fields.get(i); - try { - if (field.isOptional()) { - // Optional field -- might not be present and we may thus be trying to set - // it from the wrong tag. - try { - field.setValueFrom(dataValue, t); - nextUnreadFieldIndex = i + 1; - break; - } catch (Asn1UnexpectedTagException e) { - // This field is not present, attempt to use this data value for the - // next / iteration of the loop - continue; - } - } else { - // Mandatory field -- if we can't set its value from this data value, then - // it's an error - field.setValueFrom(dataValue, t); - nextUnreadFieldIndex = i + 1; - break; - } - } catch (Asn1DecodingException e) { - throw new Asn1DecodingException( - "Failed to parse " + containerClass.getName() - + "." + field.getField().getName(), - e); - } - } - } - - return t; - } - - // NOTE: This method returns List rather than Set because ASN.1 SET_OF does require uniqueness - // of elements -- it's an unordered collection. - @SuppressWarnings("unchecked") - private static List parseSetOf(BerDataValue container, Class elementClass) - throws Asn1DecodingException { - List result = new ArrayList<>(); - BerDataValueReader elementsReader = container.contentsReader(); - while (true) { - BerDataValue dataValue; - try { - dataValue = elementsReader.readDataValue(); - } catch (BerDataValueFormatException e) { - throw new Asn1DecodingException("Malformed data value", e); - } - if (dataValue == null) { - break; - } - T element; - if (ByteBuffer.class.equals(elementClass)) { - element = (T) dataValue.getEncodedContents(); - } else if (Asn1OpaqueObject.class.equals(elementClass)) { - element = (T) new Asn1OpaqueObject(dataValue.getEncoded()); - } else { - element = parse(dataValue, elementClass); - } - result.add(element); - } - return result; - } - - private static Asn1Type getContainerAsn1Type(Class containerClass) - throws Asn1DecodingException { - Asn1Class containerAnnotation = containerClass.getDeclaredAnnotation(Asn1Class.class); - if (containerAnnotation == null) { - throw new Asn1DecodingException( - containerClass.getName() + " is not annotated with " - + Asn1Class.class.getName()); - } - - switch (containerAnnotation.type()) { - case CHOICE: - case SEQUENCE: - case UNENCODED_CONTAINER: - return containerAnnotation.type(); - default: - throw new Asn1DecodingException( - "Unsupported ASN.1 container annotation type: " - + containerAnnotation.type()); - } - } - - private static Class getElementType(Field field) - throws Asn1DecodingException, ClassNotFoundException { - String type = field.getGenericType().getTypeName(); - int delimiterIndex = type.indexOf('<'); - if (delimiterIndex == -1) { - throw new Asn1DecodingException("Not a container type: " + field.getGenericType()); - } - int startIndex = delimiterIndex + 1; - int endIndex = type.indexOf('>', startIndex); - // TODO: handle comma? - if (endIndex == -1) { - throw new Asn1DecodingException("Not a container type: " + field.getGenericType()); - } - String elementClassName = type.substring(startIndex, endIndex); - return Class.forName(elementClassName); - } - - private static final class AnnotatedField { - private final Field mField; - private final Asn1Field mAnnotation; - private final Asn1Type mDataType; - private final Asn1TagClass mTagClass; - private final int mBerTagClass; - private final int mBerTagNumber; - private final Asn1Tagging mTagging; - private final boolean mOptional; - - public AnnotatedField(Field field, Asn1Field annotation) throws Asn1DecodingException { - mField = field; - mAnnotation = annotation; - mDataType = annotation.type(); - - Asn1TagClass tagClass = annotation.cls(); - if (tagClass == Asn1TagClass.AUTOMATIC) { - if (annotation.tagNumber() != -1) { - tagClass = Asn1TagClass.CONTEXT_SPECIFIC; - } else { - tagClass = Asn1TagClass.UNIVERSAL; - } - } - mTagClass = tagClass; - mBerTagClass = BerEncoding.getTagClass(mTagClass); - - int tagNumber; - if (annotation.tagNumber() != -1) { - tagNumber = annotation.tagNumber(); - } else if ((mDataType == Asn1Type.CHOICE) || (mDataType == Asn1Type.ANY)) { - tagNumber = -1; - } else { - tagNumber = BerEncoding.getTagNumber(mDataType); - } - mBerTagNumber = tagNumber; - - mTagging = annotation.tagging(); - if (((mTagging == Asn1Tagging.EXPLICIT) || (mTagging == Asn1Tagging.IMPLICIT)) - && (annotation.tagNumber() == -1)) { - throw new Asn1DecodingException( - "Tag number must be specified when tagging mode is " + mTagging); - } - - mOptional = annotation.optional(); - } - - public Field getField() { - return mField; - } - - public Asn1Field getAnnotation() { - return mAnnotation; - } - - public boolean isOptional() { - return mOptional; - } - - public int getBerTagClass() { - return mBerTagClass; - } - - public int getBerTagNumber() { - return mBerTagNumber; - } - - public void setValueFrom(BerDataValue dataValue, Object obj) throws Asn1DecodingException { - int readTagClass = dataValue.getTagClass(); - if (mBerTagNumber != -1) { - int readTagNumber = dataValue.getTagNumber(); - if ((readTagClass != mBerTagClass) || (readTagNumber != mBerTagNumber)) { - throw new Asn1UnexpectedTagException( - "Tag mismatch. Expected: " - + BerEncoding.tagClassAndNumberToString(mBerTagClass, mBerTagNumber) - + ", but found " - + BerEncoding.tagClassAndNumberToString(readTagClass, readTagNumber)); - } - } else { - if (readTagClass != mBerTagClass) { - throw new Asn1UnexpectedTagException( - "Tag mismatch. Expected class: " - + BerEncoding.tagClassToString(mBerTagClass) - + ", but found " - + BerEncoding.tagClassToString(readTagClass)); - } - } - - if (mTagging == Asn1Tagging.EXPLICIT) { - try { - dataValue = dataValue.contentsReader().readDataValue(); - } catch (BerDataValueFormatException e) { - throw new Asn1DecodingException( - "Failed to read contents of EXPLICIT data value", e); - } - } - - BerToJavaConverter.setFieldValue(obj, mField, mDataType, dataValue); - } - } - - private static class Asn1UnexpectedTagException extends Asn1DecodingException { - private static final long serialVersionUID = 1L; - - public Asn1UnexpectedTagException(String message) { - super(message); - } - } - - private static String oidToString(ByteBuffer encodedOid) throws Asn1DecodingException { - if (!encodedOid.hasRemaining()) { - throw new Asn1DecodingException("Empty OBJECT IDENTIFIER"); - } - - // First component encodes the first two nodes, X.Y, as X * 40 + Y, with 0 <= X <= 2 - long firstComponent = decodeBase128UnsignedLong(encodedOid); - int firstNode = (int) Math.min(firstComponent / 40, 2); - long secondNode = firstComponent - firstNode * 40; - StringBuilder result = new StringBuilder(); - result.append(Long.toString(firstNode)).append('.') - .append(Long.toString(secondNode)); - - // Each consecutive node is encoded as a separate component - while (encodedOid.hasRemaining()) { - long node = decodeBase128UnsignedLong(encodedOid); - result.append('.').append(Long.toString(node)); - } - - return result.toString(); - } - - private static long decodeBase128UnsignedLong(ByteBuffer encoded) throws Asn1DecodingException { - if (!encoded.hasRemaining()) { - return 0; - } - long result = 0; - while (encoded.hasRemaining()) { - if (result > Long.MAX_VALUE >>> 7) { - throw new Asn1DecodingException("Base-128 number too large"); - } - int b = encoded.get() & 0xff; - result <<= 7; - result |= b & 0x7f; - if ((b & 0x80) == 0) { - return result; - } - } - throw new Asn1DecodingException( - "Truncated base-128 encoded input: missing terminating byte, with highest bit not" - + " set"); - } - - private static BigInteger integerToBigInteger(ByteBuffer encoded) { - if (!encoded.hasRemaining()) { - return BigInteger.ZERO; - } - return new BigInteger(ByteBufferUtils.toByteArray(encoded)); - } - - private static int integerToInt(ByteBuffer encoded) throws Asn1DecodingException { - BigInteger value = integerToBigInteger(encoded); - if (value.compareTo(BigInteger.valueOf(Integer.MIN_VALUE)) < 0 - || value.compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) > 0) { - throw new Asn1DecodingException( - String.format("INTEGER cannot be represented as int: %1$d (0x%1$x)", value)); - } - return value.intValue(); - } - - private static long integerToLong(ByteBuffer encoded) throws Asn1DecodingException { - BigInteger value = integerToBigInteger(encoded); - if (value.compareTo(BigInteger.valueOf(Long.MIN_VALUE)) < 0 - || value.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0) { - throw new Asn1DecodingException( - String.format("INTEGER cannot be represented as long: %1$d (0x%1$x)", value)); - } - return value.longValue(); - } - - private static List getAnnotatedFields(Class containerClass) - throws Asn1DecodingException { - Field[] declaredFields = containerClass.getDeclaredFields(); - List result = new ArrayList<>(declaredFields.length); - for (Field field : declaredFields) { - Asn1Field annotation = field.getDeclaredAnnotation(Asn1Field.class); - if (annotation == null) { - continue; - } - if (Modifier.isStatic(field.getModifiers())) { - throw new Asn1DecodingException( - Asn1Field.class.getName() + " used on a static field: " - + containerClass.getName() + "." + field.getName()); - } - - AnnotatedField annotatedField; - try { - annotatedField = new AnnotatedField(field, annotation); - } catch (Asn1DecodingException e) { - throw new Asn1DecodingException( - "Invalid ASN.1 annotation on " - + containerClass.getName() + "." + field.getName(), - e); - } - result.add(annotatedField); - } - return result; - } - - private static final class BerToJavaConverter { - private BerToJavaConverter() {} - - public static void setFieldValue( - Object obj, Field field, Asn1Type type, BerDataValue dataValue) - throws Asn1DecodingException { - try { - switch (type) { - case SET_OF: - case SEQUENCE_OF: - if (Asn1OpaqueObject.class.equals(field.getType())) { - field.set(obj, convert(type, dataValue, field.getType())); - } else { - field.set(obj, parseSetOf(dataValue, getElementType(field))); - } - return; - default: - field.set(obj, convert(type, dataValue, field.getType())); - break; - } - } catch (ReflectiveOperationException e) { - throw new Asn1DecodingException( - "Failed to set value of " + obj.getClass().getName() - + "." + field.getName(), - e); - } - } - - private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; - - @SuppressWarnings("unchecked") - public static T convert( - Asn1Type sourceType, - BerDataValue dataValue, - Class targetType) throws Asn1DecodingException { - if (ByteBuffer.class.equals(targetType)) { - return (T) dataValue.getEncodedContents(); - } else if (byte[].class.equals(targetType)) { - ByteBuffer resultBuf = dataValue.getEncodedContents(); - if (!resultBuf.hasRemaining()) { - return (T) EMPTY_BYTE_ARRAY; - } - byte[] result = new byte[resultBuf.remaining()]; - resultBuf.get(result); - return (T) result; - } else if (Asn1OpaqueObject.class.equals(targetType)) { - return (T) new Asn1OpaqueObject(dataValue.getEncoded()); - } - ByteBuffer encodedContents = dataValue.getEncodedContents(); - switch (sourceType) { - case INTEGER: - if ((int.class.equals(targetType)) || (Integer.class.equals(targetType))) { - return (T) Integer.valueOf(integerToInt(encodedContents)); - } else if ((long.class.equals(targetType)) || (Long.class.equals(targetType))) { - return (T) Long.valueOf(integerToLong(encodedContents)); - } else if (BigInteger.class.equals(targetType)) { - return (T) integerToBigInteger(encodedContents); - } - break; - case OBJECT_IDENTIFIER: - if (String.class.equals(targetType)) { - return (T) oidToString(encodedContents); - } - break; - case UTC_TIME: - case GENERALIZED_TIME: - if (String.class.equals(targetType)) { - return (T) new String(ByteBufferUtils.toByteArray(encodedContents)); - } - break; - case BOOLEAN: - // A boolean should be encoded in a single byte with a value of 0 for false and - // any non-zero value for true. - if (boolean.class.equals(targetType)) { - if (encodedContents.remaining() != 1) { - throw new Asn1DecodingException( - "Incorrect encoded size of boolean value: " - + encodedContents.remaining()); - } - boolean result; - if (encodedContents.get() == 0) { - result = false; - } else { - result = true; - } - return (T) new Boolean(result); - } - break; - case SEQUENCE: - { - Asn1Class containerAnnotation = - targetType.getDeclaredAnnotation(Asn1Class.class); - if ((containerAnnotation != null) - && (containerAnnotation.type() == Asn1Type.SEQUENCE)) { - return parseSequence(dataValue, targetType); - } - break; - } - case CHOICE: - { - Asn1Class containerAnnotation = - targetType.getDeclaredAnnotation(Asn1Class.class); - if ((containerAnnotation != null) - && (containerAnnotation.type() == Asn1Type.CHOICE)) { - return parseChoice(dataValue, targetType); - } - break; - } - default: - break; - } - - throw new Asn1DecodingException( - "Unsupported conversion: ASN.1 " + sourceType + " to " + targetType.getName()); - } - } -} diff --git a/app/src/main/java/com/android/apksig/internal/asn1/Asn1Class.java b/app/src/main/java/com/android/apksig/internal/asn1/Asn1Class.java deleted file mode 100644 index 4841296c6b..0000000000 --- a/app/src/main/java/com/android/apksig/internal/asn1/Asn1Class.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.asn1; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target({ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -public @interface Asn1Class { - public Asn1Type type(); -} diff --git a/app/src/main/java/com/android/apksig/internal/asn1/Asn1DecodingException.java b/app/src/main/java/com/android/apksig/internal/asn1/Asn1DecodingException.java deleted file mode 100644 index 07886429bf..0000000000 --- a/app/src/main/java/com/android/apksig/internal/asn1/Asn1DecodingException.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.asn1; - -/** - * Indicates that input could not be decoded into intended ASN.1 structure. - */ -public class Asn1DecodingException extends Exception { - private static final long serialVersionUID = 1L; - - public Asn1DecodingException(String message) { - super(message); - } - - public Asn1DecodingException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/app/src/main/java/com/android/apksig/internal/asn1/Asn1DerEncoder.java b/app/src/main/java/com/android/apksig/internal/asn1/Asn1DerEncoder.java deleted file mode 100644 index 901f5f30c0..0000000000 --- a/app/src/main/java/com/android/apksig/internal/asn1/Asn1DerEncoder.java +++ /dev/null @@ -1,596 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.asn1; - -import com.android.apksig.internal.asn1.ber.BerEncoding; - -import java.io.ByteArrayOutputStream; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.math.BigInteger; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -/** - * Encoder of ASN.1 structures into DER-encoded form. - * - *

Structure is described to the encoder by providing a class annotated with {@link Asn1Class}, - * containing fields annotated with {@link Asn1Field}. - */ -public final class Asn1DerEncoder { - private Asn1DerEncoder() {} - - /** - * Returns the DER-encoded form of the provided ASN.1 structure. - * - * @param container container to be encoded. The container's class must meet the following - * requirements: - *

    - *
  • The class must be annotated with {@link Asn1Class}.
  • - *
  • Member fields of the class which are to be encoded must be annotated with - * {@link Asn1Field} and be public.
  • - *
- * - * @throws Asn1EncodingException if the input could not be encoded - */ - public static byte[] encode(Object container) throws Asn1EncodingException { - Class containerClass = container.getClass(); - Asn1Class containerAnnotation = containerClass.getDeclaredAnnotation(Asn1Class.class); - if (containerAnnotation == null) { - throw new Asn1EncodingException( - containerClass.getName() + " not annotated with " + Asn1Class.class.getName()); - } - - Asn1Type containerType = containerAnnotation.type(); - switch (containerType) { - case CHOICE: - return toChoice(container); - case SEQUENCE: - return toSequence(container); - case UNENCODED_CONTAINER: - return toSequence(container, true); - default: - throw new Asn1EncodingException("Unsupported container type: " + containerType); - } - } - - private static byte[] toChoice(Object container) throws Asn1EncodingException { - Class containerClass = container.getClass(); - List fields = getAnnotatedFields(container); - if (fields.isEmpty()) { - throw new Asn1EncodingException( - "No fields annotated with " + Asn1Field.class.getName() - + " in CHOICE class " + containerClass.getName()); - } - - AnnotatedField resultField = null; - for (AnnotatedField field : fields) { - Object fieldValue = getMemberFieldValue(container, field.getField()); - if (fieldValue != null) { - if (resultField != null) { - throw new Asn1EncodingException( - "Multiple non-null fields in CHOICE class " + containerClass.getName() - + ": " + resultField.getField().getName() - + ", " + field.getField().getName()); - } - resultField = field; - } - } - - if (resultField == null) { - throw new Asn1EncodingException( - "No non-null fields in CHOICE class " + containerClass.getName()); - } - - return resultField.toDer(); - } - - private static byte[] toSequence(Object container) throws Asn1EncodingException { - return toSequence(container, false); - } - - private static byte[] toSequence(Object container, boolean omitTag) - throws Asn1EncodingException { - Class containerClass = container.getClass(); - List fields = getAnnotatedFields(container); - Collections.sort( - fields, (f1, f2) -> f1.getAnnotation().index() - f2.getAnnotation().index()); - if (fields.size() > 1) { - AnnotatedField lastField = null; - for (AnnotatedField field : fields) { - if ((lastField != null) - && (lastField.getAnnotation().index() == field.getAnnotation().index())) { - throw new Asn1EncodingException( - "Fields have the same index: " + containerClass.getName() - + "." + lastField.getField().getName() - + " and ." + field.getField().getName()); - } - lastField = field; - } - } - - List serializedFields = new ArrayList<>(fields.size()); - int contentLen = 0; - for (AnnotatedField field : fields) { - byte[] serializedField; - try { - serializedField = field.toDer(); - } catch (Asn1EncodingException e) { - throw new Asn1EncodingException( - "Failed to encode " + containerClass.getName() - + "." + field.getField().getName(), - e); - } - if (serializedField != null) { - serializedFields.add(serializedField); - contentLen += serializedField.length; - } - } - - if (omitTag) { - byte[] unencodedResult = new byte[contentLen]; - int index = 0; - for (byte[] serializedField : serializedFields) { - System.arraycopy(serializedField, 0, unencodedResult, index, serializedField.length); - index += serializedField.length; - } - return unencodedResult; - } else { - return createTag( - BerEncoding.TAG_CLASS_UNIVERSAL, true, BerEncoding.TAG_NUMBER_SEQUENCE, - serializedFields.toArray(new byte[0][])); - } - } - - private static byte[] toSetOf(Collection values, Asn1Type elementType) throws Asn1EncodingException { - return toSequenceOrSetOf(values, elementType, true); - } - - private static byte[] toSequenceOf(Collection values, Asn1Type elementType) throws Asn1EncodingException { - return toSequenceOrSetOf(values, elementType, false); - } - - private static byte[] toSequenceOrSetOf(Collection values, Asn1Type elementType, boolean toSet) - throws Asn1EncodingException { - List serializedValues = new ArrayList<>(values.size()); - for (Object value : values) { - serializedValues.add(JavaToDerConverter.toDer(value, elementType, null)); - } - int tagNumber; - if (toSet) { - if (serializedValues.size() > 1) { - Collections.sort(serializedValues, ByteArrayLexicographicComparator.INSTANCE); - } - tagNumber = BerEncoding.TAG_NUMBER_SET; - } else { - tagNumber = BerEncoding.TAG_NUMBER_SEQUENCE; - } - return createTag( - BerEncoding.TAG_CLASS_UNIVERSAL, true, tagNumber, - serializedValues.toArray(new byte[0][])); - } - - /** - * Compares two bytes arrays based on their lexicographic order. Corresponding elements of the - * two arrays are compared in ascending order. Elements at out of range indices are assumed to - * be smaller than the smallest possible value for an element. - */ - private static class ByteArrayLexicographicComparator implements Comparator { - private static final ByteArrayLexicographicComparator INSTANCE = - new ByteArrayLexicographicComparator(); - - @Override - public int compare(byte[] arr1, byte[] arr2) { - int commonLength = Math.min(arr1.length, arr2.length); - for (int i = 0; i < commonLength; i++) { - int diff = (arr1[i] & 0xff) - (arr2[i] & 0xff); - if (diff != 0) { - return diff; - } - } - return arr1.length - arr2.length; - } - } - - private static List getAnnotatedFields(Object container) - throws Asn1EncodingException { - Class containerClass = container.getClass(); - Field[] declaredFields = containerClass.getDeclaredFields(); - List result = new ArrayList<>(declaredFields.length); - for (Field field : declaredFields) { - Asn1Field annotation = field.getDeclaredAnnotation(Asn1Field.class); - if (annotation == null) { - continue; - } - if (Modifier.isStatic(field.getModifiers())) { - throw new Asn1EncodingException( - Asn1Field.class.getName() + " used on a static field: " - + containerClass.getName() + "." + field.getName()); - } - - AnnotatedField annotatedField; - try { - annotatedField = new AnnotatedField(container, field, annotation); - } catch (Asn1EncodingException e) { - throw new Asn1EncodingException( - "Invalid ASN.1 annotation on " - + containerClass.getName() + "." + field.getName(), - e); - } - result.add(annotatedField); - } - return result; - } - - private static byte[] toInteger(int value) { - return toInteger((long) value); - } - - private static byte[] toInteger(long value) { - return toInteger(BigInteger.valueOf(value)); - } - - private static byte[] toInteger(BigInteger value) { - return createTag( - BerEncoding.TAG_CLASS_UNIVERSAL, false, BerEncoding.TAG_NUMBER_INTEGER, - value.toByteArray()); - } - - private static byte[] toBoolean(boolean value) { - // A boolean should be encoded in a single byte with a value of 0 for false and any non-zero - // value for true. - byte[] result = new byte[1]; - if (value == false) { - result[0] = 0; - } else { - result[0] = 1; - } - return createTag(BerEncoding.TAG_CLASS_UNIVERSAL, false, BerEncoding.TAG_NUMBER_BOOLEAN, result); - } - - private static byte[] toOid(String oid) throws Asn1EncodingException { - ByteArrayOutputStream encodedValue = new ByteArrayOutputStream(); - String[] nodes = oid.split("\\."); - if (nodes.length < 2) { - throw new Asn1EncodingException( - "OBJECT IDENTIFIER must contain at least two nodes: " + oid); - } - int firstNode; - try { - firstNode = Integer.parseInt(nodes[0]); - } catch (NumberFormatException e) { - throw new Asn1EncodingException("Node #1 not numeric: " + nodes[0]); - } - if ((firstNode > 6) || (firstNode < 0)) { - throw new Asn1EncodingException("Invalid value for node #1: " + firstNode); - } - - int secondNode; - try { - secondNode = Integer.parseInt(nodes[1]); - } catch (NumberFormatException e) { - throw new Asn1EncodingException("Node #2 not numeric: " + nodes[1]); - } - if ((secondNode >= 40) || (secondNode < 0)) { - throw new Asn1EncodingException("Invalid value for node #2: " + secondNode); - } - int firstByte = firstNode * 40 + secondNode; - if (firstByte > 0xff) { - throw new Asn1EncodingException( - "First two nodes out of range: " + firstNode + "." + secondNode); - } - - encodedValue.write(firstByte); - for (int i = 2; i < nodes.length; i++) { - String nodeString = nodes[i]; - int node; - try { - node = Integer.parseInt(nodeString); - } catch (NumberFormatException e) { - throw new Asn1EncodingException("Node #" + (i + 1) + " not numeric: " + nodeString); - } - if (node < 0) { - throw new Asn1EncodingException("Invalid value for node #" + (i + 1) + ": " + node); - } - if (node <= 0x7f) { - encodedValue.write(node); - continue; - } - if (node < 1 << 14) { - encodedValue.write(0x80 | (node >> 7)); - encodedValue.write(node & 0x7f); - continue; - } - if (node < 1 << 21) { - encodedValue.write(0x80 | (node >> 14)); - encodedValue.write(0x80 | ((node >> 7) & 0x7f)); - encodedValue.write(node & 0x7f); - continue; - } - throw new Asn1EncodingException("Node #" + (i + 1) + " too large: " + node); - } - - return createTag( - BerEncoding.TAG_CLASS_UNIVERSAL, false, BerEncoding.TAG_NUMBER_OBJECT_IDENTIFIER, - encodedValue.toByteArray()); - } - - private static Object getMemberFieldValue(Object obj, Field field) - throws Asn1EncodingException { - try { - return field.get(obj); - } catch (ReflectiveOperationException e) { - throw new Asn1EncodingException( - "Failed to read " + obj.getClass().getName() + "." + field.getName(), e); - } - } - - private static final class AnnotatedField { - private final Field mField; - private final Object mObject; - private final Asn1Field mAnnotation; - private final Asn1Type mDataType; - private final Asn1Type mElementDataType; - private final Asn1TagClass mTagClass; - private final int mDerTagClass; - private final int mDerTagNumber; - private final Asn1Tagging mTagging; - private final boolean mOptional; - - public AnnotatedField(Object obj, Field field, Asn1Field annotation) - throws Asn1EncodingException { - mObject = obj; - mField = field; - mAnnotation = annotation; - mDataType = annotation.type(); - mElementDataType = annotation.elementType(); - - Asn1TagClass tagClass = annotation.cls(); - if (tagClass == Asn1TagClass.AUTOMATIC) { - if (annotation.tagNumber() != -1) { - tagClass = Asn1TagClass.CONTEXT_SPECIFIC; - } else { - tagClass = Asn1TagClass.UNIVERSAL; - } - } - mTagClass = tagClass; - mDerTagClass = BerEncoding.getTagClass(mTagClass); - - int tagNumber; - if (annotation.tagNumber() != -1) { - tagNumber = annotation.tagNumber(); - } else if ((mDataType == Asn1Type.CHOICE) || (mDataType == Asn1Type.ANY)) { - tagNumber = -1; - } else { - tagNumber = BerEncoding.getTagNumber(mDataType); - } - mDerTagNumber = tagNumber; - - mTagging = annotation.tagging(); - if (((mTagging == Asn1Tagging.EXPLICIT) || (mTagging == Asn1Tagging.IMPLICIT)) - && (annotation.tagNumber() == -1)) { - throw new Asn1EncodingException( - "Tag number must be specified when tagging mode is " + mTagging); - } - - mOptional = annotation.optional(); - } - - public Field getField() { - return mField; - } - - public Asn1Field getAnnotation() { - return mAnnotation; - } - - public byte[] toDer() throws Asn1EncodingException { - Object fieldValue = getMemberFieldValue(mObject, mField); - if (fieldValue == null) { - if (mOptional) { - return null; - } - throw new Asn1EncodingException("Required field not set"); - } - - byte[] encoded = JavaToDerConverter.toDer(fieldValue, mDataType, mElementDataType); - switch (mTagging) { - case NORMAL: - return encoded; - case EXPLICIT: - return createTag(mDerTagClass, true, mDerTagNumber, encoded); - case IMPLICIT: - int originalTagNumber = BerEncoding.getTagNumber(encoded[0]); - if (originalTagNumber == 0x1f) { - throw new Asn1EncodingException("High-tag-number form not supported"); - } - if (mDerTagNumber >= 0x1f) { - throw new Asn1EncodingException( - "Unsupported high tag number: " + mDerTagNumber); - } - encoded[0] = BerEncoding.setTagNumber(encoded[0], mDerTagNumber); - encoded[0] = BerEncoding.setTagClass(encoded[0], mDerTagClass); - return encoded; - default: - throw new RuntimeException("Unknown tagging mode: " + mTagging); - } - } - } - - private static byte[] createTag( - int tagClass, boolean constructed, int tagNumber, byte[]... contents) { - if (tagNumber >= 0x1f) { - throw new IllegalArgumentException("High tag numbers not supported: " + tagNumber); - } - // tag class & number fit into the first byte - byte firstIdentifierByte = - (byte) ((tagClass << 6) | (constructed ? 1 << 5 : 0) | tagNumber); - - int contentsLength = 0; - for (byte[] c : contents) { - contentsLength += c.length; - } - int contentsPosInResult; - byte[] result; - if (contentsLength < 0x80) { - // Length fits into one byte - contentsPosInResult = 2; - result = new byte[contentsPosInResult + contentsLength]; - result[0] = firstIdentifierByte; - result[1] = (byte) contentsLength; - } else { - // Length is represented as multiple bytes - // The low 7 bits of the first byte represent the number of length bytes (following the - // first byte) in which the length is in big-endian base-256 form - if (contentsLength <= 0xff) { - contentsPosInResult = 3; - result = new byte[contentsPosInResult + contentsLength]; - result[1] = (byte) 0x81; // 1 length byte - result[2] = (byte) contentsLength; - } else if (contentsLength <= 0xffff) { - contentsPosInResult = 4; - result = new byte[contentsPosInResult + contentsLength]; - result[1] = (byte) 0x82; // 2 length bytes - result[2] = (byte) (contentsLength >> 8); - result[3] = (byte) (contentsLength & 0xff); - } else if (contentsLength <= 0xffffff) { - contentsPosInResult = 5; - result = new byte[contentsPosInResult + contentsLength]; - result[1] = (byte) 0x83; // 3 length bytes - result[2] = (byte) (contentsLength >> 16); - result[3] = (byte) ((contentsLength >> 8) & 0xff); - result[4] = (byte) (contentsLength & 0xff); - } else { - contentsPosInResult = 6; - result = new byte[contentsPosInResult + contentsLength]; - result[1] = (byte) 0x84; // 4 length bytes - result[2] = (byte) (contentsLength >> 24); - result[3] = (byte) ((contentsLength >> 16) & 0xff); - result[4] = (byte) ((contentsLength >> 8) & 0xff); - result[5] = (byte) (contentsLength & 0xff); - } - result[0] = firstIdentifierByte; - } - for (byte[] c : contents) { - System.arraycopy(c, 0, result, contentsPosInResult, c.length); - contentsPosInResult += c.length; - } - return result; - } - - private static final class JavaToDerConverter { - private JavaToDerConverter() {} - - public static byte[] toDer(Object source, Asn1Type targetType, Asn1Type targetElementType) - throws Asn1EncodingException { - Class sourceType = source.getClass(); - if (Asn1OpaqueObject.class.equals(sourceType)) { - ByteBuffer buf = ((Asn1OpaqueObject) source).getEncoded(); - byte[] result = new byte[buf.remaining()]; - buf.get(result); - return result; - } - - if ((targetType == null) || (targetType == Asn1Type.ANY)) { - return encode(source); - } - - switch (targetType) { - case OCTET_STRING: - case BIT_STRING: - byte[] value = null; - if (source instanceof ByteBuffer) { - ByteBuffer buf = (ByteBuffer) source; - value = new byte[buf.remaining()]; - buf.slice().get(value); - } else if (source instanceof byte[]) { - value = (byte[]) source; - } - if (value != null) { - return createTag( - BerEncoding.TAG_CLASS_UNIVERSAL, - false, - BerEncoding.getTagNumber(targetType), - value); - } - break; - case INTEGER: - if (source instanceof Integer) { - return toInteger((Integer) source); - } else if (source instanceof Long) { - return toInteger((Long) source); - } else if (source instanceof BigInteger) { - return toInteger((BigInteger) source); - } - break; - case BOOLEAN: - if (source instanceof Boolean) { - return toBoolean((Boolean) (source)); - } - break; - case UTC_TIME: - case GENERALIZED_TIME: - if (source instanceof String) { - return createTag(BerEncoding.TAG_CLASS_UNIVERSAL, false, - BerEncoding.getTagNumber(targetType), ((String) source).getBytes()); - } - break; - case OBJECT_IDENTIFIER: - if (source instanceof String) { - return toOid((String) source); - } - break; - case SEQUENCE: - { - Asn1Class containerAnnotation = - sourceType.getDeclaredAnnotation(Asn1Class.class); - if ((containerAnnotation != null) - && (containerAnnotation.type() == Asn1Type.SEQUENCE)) { - return toSequence(source); - } - break; - } - case CHOICE: - { - Asn1Class containerAnnotation = - sourceType.getDeclaredAnnotation(Asn1Class.class); - if ((containerAnnotation != null) - && (containerAnnotation.type() == Asn1Type.CHOICE)) { - return toChoice(source); - } - break; - } - case SET_OF: - return toSetOf((Collection) source, targetElementType); - case SEQUENCE_OF: - return toSequenceOf((Collection) source, targetElementType); - default: - break; - } - - throw new Asn1EncodingException( - "Unsupported conversion: " + sourceType.getName() + " to ASN.1 " + targetType); - } - } - /** ASN.1 DER-encoded {@code NULL}. */ - public static final Asn1OpaqueObject ASN1_DER_NULL = - new Asn1OpaqueObject(new byte[] {BerEncoding.TAG_NUMBER_NULL, 0}); -} diff --git a/app/src/main/java/com/android/apksig/internal/asn1/Asn1EncodingException.java b/app/src/main/java/com/android/apksig/internal/asn1/Asn1EncodingException.java deleted file mode 100644 index 0002c25cba..0000000000 --- a/app/src/main/java/com/android/apksig/internal/asn1/Asn1EncodingException.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.asn1; - -/** - * Indicates that an ASN.1 structure could not be encoded. - */ -public class Asn1EncodingException extends Exception { - private static final long serialVersionUID = 1L; - - public Asn1EncodingException(String message) { - super(message); - } - - public Asn1EncodingException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/app/src/main/java/com/android/apksig/internal/asn1/Asn1Field.java b/app/src/main/java/com/android/apksig/internal/asn1/Asn1Field.java deleted file mode 100644 index d2d3ce049e..0000000000 --- a/app/src/main/java/com/android/apksig/internal/asn1/Asn1Field.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.asn1; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target({ElementType.FIELD}) -@Retention(RetentionPolicy.RUNTIME) -public @interface Asn1Field { - /** Index used to order fields in a container. Required for fields of SEQUENCE containers. */ - public int index() default 0; - - public Asn1TagClass cls() default Asn1TagClass.AUTOMATIC; - - public Asn1Type type(); - - /** Tagging mode. Default: NORMAL. */ - public Asn1Tagging tagging() default Asn1Tagging.NORMAL; - - /** Tag number. Required when IMPLICIT and EXPLICIT tagging mode is used.*/ - public int tagNumber() default -1; - - /** {@code true} if this field is optional. Ignored for fields of CHOICE containers. */ - public boolean optional() default false; - - /** Type of elements. Used only for SET_OF or SEQUENCE_OF. */ - public Asn1Type elementType() default Asn1Type.ANY; -} diff --git a/app/src/main/java/com/android/apksig/internal/asn1/Asn1OpaqueObject.java b/app/src/main/java/com/android/apksig/internal/asn1/Asn1OpaqueObject.java deleted file mode 100644 index 672d0e74c6..0000000000 --- a/app/src/main/java/com/android/apksig/internal/asn1/Asn1OpaqueObject.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.asn1; - -import java.nio.ByteBuffer; - -/** - * Opaque holder of encoded ASN.1 stuff. - */ -public class Asn1OpaqueObject { - private final ByteBuffer mEncoded; - - public Asn1OpaqueObject(ByteBuffer encoded) { - mEncoded = encoded.slice(); - } - - public Asn1OpaqueObject(byte[] encoded) { - mEncoded = ByteBuffer.wrap(encoded); - } - - public ByteBuffer getEncoded() { - return mEncoded.slice(); - } -} diff --git a/app/src/main/java/com/android/apksig/internal/asn1/Asn1TagClass.java b/app/src/main/java/com/android/apksig/internal/asn1/Asn1TagClass.java deleted file mode 100644 index 6cdfcf014c..0000000000 --- a/app/src/main/java/com/android/apksig/internal/asn1/Asn1TagClass.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.asn1; - -public enum Asn1TagClass { - UNIVERSAL, - APPLICATION, - CONTEXT_SPECIFIC, - PRIVATE, - - /** - * Not really an actual tag class: decoder/encoder will attempt to deduce the correct tag class - * automatically. - */ - AUTOMATIC, -} diff --git a/app/src/main/java/com/android/apksig/internal/asn1/Asn1Tagging.java b/app/src/main/java/com/android/apksig/internal/asn1/Asn1Tagging.java deleted file mode 100644 index 35fa3744e1..0000000000 --- a/app/src/main/java/com/android/apksig/internal/asn1/Asn1Tagging.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.asn1; - -public enum Asn1Tagging { - NORMAL, - EXPLICIT, - IMPLICIT, -} diff --git a/app/src/main/java/com/android/apksig/internal/asn1/Asn1Type.java b/app/src/main/java/com/android/apksig/internal/asn1/Asn1Type.java deleted file mode 100644 index 73006222b2..0000000000 --- a/app/src/main/java/com/android/apksig/internal/asn1/Asn1Type.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.asn1; - -public enum Asn1Type { - ANY, - CHOICE, - INTEGER, - OBJECT_IDENTIFIER, - OCTET_STRING, - SEQUENCE, - SEQUENCE_OF, - SET_OF, - BIT_STRING, - UTC_TIME, - GENERALIZED_TIME, - BOOLEAN, - // This type can be used to annotate classes that encapsulate ASN.1 structures that are not - // classified as a SEQUENCE or SET. - UNENCODED_CONTAINER -} diff --git a/app/src/main/java/com/android/apksig/internal/asn1/ber/BerDataValue.java b/app/src/main/java/com/android/apksig/internal/asn1/ber/BerDataValue.java deleted file mode 100644 index f5604ffdfb..0000000000 --- a/app/src/main/java/com/android/apksig/internal/asn1/ber/BerDataValue.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.asn1.ber; - -import java.nio.ByteBuffer; - -/** - * ASN.1 Basic Encoding Rules (BER) data value -- see {@code X.690}. - */ -public class BerDataValue { - private final ByteBuffer mEncoded; - private final ByteBuffer mEncodedContents; - private final int mTagClass; - private final boolean mConstructed; - private final int mTagNumber; - - BerDataValue( - ByteBuffer encoded, - ByteBuffer encodedContents, - int tagClass, - boolean constructed, - int tagNumber) { - mEncoded = encoded; - mEncodedContents = encodedContents; - mTagClass = tagClass; - mConstructed = constructed; - mTagNumber = tagNumber; - } - - /** - * Returns the tag class of this data value. See {@link BerEncoding} {@code TAG_CLASS} - * constants. - */ - public int getTagClass() { - return mTagClass; - } - - /** - * Returns {@code true} if the content octets of this data value are the complete BER encoding - * of one or more data values, {@code false} if the content octets of this data value directly - * represent the value. - */ - public boolean isConstructed() { - return mConstructed; - } - - /** - * Returns the tag number of this data value. See {@link BerEncoding} {@code TAG_NUMBER} - * constants. - */ - public int getTagNumber() { - return mTagNumber; - } - - /** - * Returns the encoded form of this data value. - */ - public ByteBuffer getEncoded() { - return mEncoded.slice(); - } - - /** - * Returns the encoded contents of this data value. - */ - public ByteBuffer getEncodedContents() { - return mEncodedContents.slice(); - } - - /** - * Returns a new reader of the contents of this data value. - */ - public BerDataValueReader contentsReader() { - return new ByteBufferBerDataValueReader(getEncodedContents()); - } - - /** - * Returns a new reader which returns just this data value. This may be useful for re-reading - * this value in different contexts. - */ - public BerDataValueReader dataValueReader() { - return new ParsedValueReader(this); - } - - private static final class ParsedValueReader implements BerDataValueReader { - private final BerDataValue mValue; - private boolean mValueOutput; - - public ParsedValueReader(BerDataValue value) { - mValue = value; - } - - @Override - public BerDataValue readDataValue() throws BerDataValueFormatException { - if (mValueOutput) { - return null; - } - mValueOutput = true; - return mValue; - } - } -} diff --git a/app/src/main/java/com/android/apksig/internal/asn1/ber/BerDataValueFormatException.java b/app/src/main/java/com/android/apksig/internal/asn1/ber/BerDataValueFormatException.java deleted file mode 100644 index 11ef6c3672..0000000000 --- a/app/src/main/java/com/android/apksig/internal/asn1/ber/BerDataValueFormatException.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.asn1.ber; - -/** - * Indicates that an ASN.1 data value being read could not be decoded using - * Basic Encoding Rules (BER). - */ -public class BerDataValueFormatException extends Exception { - - private static final long serialVersionUID = 1L; - - public BerDataValueFormatException(String message) { - super(message); - } - - public BerDataValueFormatException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/app/src/main/java/com/android/apksig/internal/asn1/ber/BerDataValueReader.java b/app/src/main/java/com/android/apksig/internal/asn1/ber/BerDataValueReader.java deleted file mode 100644 index 8da0a428be..0000000000 --- a/app/src/main/java/com/android/apksig/internal/asn1/ber/BerDataValueReader.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.asn1.ber; - -/** - * Reader of ASN.1 Basic Encoding Rules (BER) data values. - * - *

BER data value reader returns data values, one by one, from a source. The interpretation of - * data values (e.g., how to obtain a numeric value from an INTEGER data value, or how to extract - * the elements of a SEQUENCE value) is left to clients of the reader. - */ -public interface BerDataValueReader { - - /** - * Returns the next data value or {@code null} if end of input has been reached. - * - * @throws BerDataValueFormatException if the value being read is malformed. - */ - BerDataValue readDataValue() throws BerDataValueFormatException; -} diff --git a/app/src/main/java/com/android/apksig/internal/asn1/ber/BerEncoding.java b/app/src/main/java/com/android/apksig/internal/asn1/ber/BerEncoding.java deleted file mode 100644 index d32330c0ad..0000000000 --- a/app/src/main/java/com/android/apksig/internal/asn1/ber/BerEncoding.java +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.asn1.ber; - -import com.android.apksig.internal.asn1.Asn1Type; -import com.android.apksig.internal.asn1.Asn1TagClass; - -/** - * ASN.1 Basic Encoding Rules (BER) constants and helper methods. See {@code X.690}. - */ -public abstract class BerEncoding { - private BerEncoding() {} - - /** - * Constructed vs primitive flag in the first identifier byte. - */ - public static final int ID_FLAG_CONSTRUCTED_ENCODING = 1 << 5; - - /** - * Tag class: UNIVERSAL - */ - public static final int TAG_CLASS_UNIVERSAL = 0; - - /** - * Tag class: APPLICATION - */ - public static final int TAG_CLASS_APPLICATION = 1; - - /** - * Tag class: CONTEXT SPECIFIC - */ - public static final int TAG_CLASS_CONTEXT_SPECIFIC = 2; - - /** - * Tag class: PRIVATE - */ - public static final int TAG_CLASS_PRIVATE = 3; - - /** - * Tag number: BOOLEAN - */ - public static final int TAG_NUMBER_BOOLEAN = 0x1; - - /** - * Tag number: INTEGER - */ - public static final int TAG_NUMBER_INTEGER = 0x2; - - /** - * Tag number: BIT STRING - */ - public static final int TAG_NUMBER_BIT_STRING = 0x3; - - /** - * Tag number: OCTET STRING - */ - public static final int TAG_NUMBER_OCTET_STRING = 0x4; - - /** - * Tag number: NULL - */ - public static final int TAG_NUMBER_NULL = 0x05; - - /** - * Tag number: OBJECT IDENTIFIER - */ - public static final int TAG_NUMBER_OBJECT_IDENTIFIER = 0x6; - - /** - * Tag number: SEQUENCE - */ - public static final int TAG_NUMBER_SEQUENCE = 0x10; - - /** - * Tag number: SET - */ - public static final int TAG_NUMBER_SET = 0x11; - - /** - * Tag number: UTC_TIME - */ - public final static int TAG_NUMBER_UTC_TIME = 0x17; - - /** - * Tag number: GENERALIZED_TIME - */ - public final static int TAG_NUMBER_GENERALIZED_TIME = 0x18; - - public static int getTagNumber(Asn1Type dataType) { - switch (dataType) { - case INTEGER: - return TAG_NUMBER_INTEGER; - case OBJECT_IDENTIFIER: - return TAG_NUMBER_OBJECT_IDENTIFIER; - case OCTET_STRING: - return TAG_NUMBER_OCTET_STRING; - case BIT_STRING: - return TAG_NUMBER_BIT_STRING; - case SET_OF: - return TAG_NUMBER_SET; - case SEQUENCE: - case SEQUENCE_OF: - return TAG_NUMBER_SEQUENCE; - case UTC_TIME: - return TAG_NUMBER_UTC_TIME; - case GENERALIZED_TIME: - return TAG_NUMBER_GENERALIZED_TIME; - case BOOLEAN: - return TAG_NUMBER_BOOLEAN; - default: - throw new IllegalArgumentException("Unsupported data type: " + dataType); - } - } - - public static int getTagClass(Asn1TagClass tagClass) { - switch (tagClass) { - case APPLICATION: - return TAG_CLASS_APPLICATION; - case CONTEXT_SPECIFIC: - return TAG_CLASS_CONTEXT_SPECIFIC; - case PRIVATE: - return TAG_CLASS_PRIVATE; - case UNIVERSAL: - return TAG_CLASS_UNIVERSAL; - default: - throw new IllegalArgumentException("Unsupported tag class: " + tagClass); - } - } - - public static String tagClassToString(int typeClass) { - switch (typeClass) { - case TAG_CLASS_APPLICATION: - return "APPLICATION"; - case TAG_CLASS_CONTEXT_SPECIFIC: - return ""; - case TAG_CLASS_PRIVATE: - return "PRIVATE"; - case TAG_CLASS_UNIVERSAL: - return "UNIVERSAL"; - default: - throw new IllegalArgumentException("Unsupported type class: " + typeClass); - } - } - - public static String tagClassAndNumberToString(int tagClass, int tagNumber) { - String classString = tagClassToString(tagClass); - String numberString = tagNumberToString(tagNumber); - return classString.isEmpty() ? numberString : classString + " " + numberString; - } - - - public static String tagNumberToString(int tagNumber) { - switch (tagNumber) { - case TAG_NUMBER_INTEGER: - return "INTEGER"; - case TAG_NUMBER_OCTET_STRING: - return "OCTET STRING"; - case TAG_NUMBER_BIT_STRING: - return "BIT STRING"; - case TAG_NUMBER_NULL: - return "NULL"; - case TAG_NUMBER_OBJECT_IDENTIFIER: - return "OBJECT IDENTIFIER"; - case TAG_NUMBER_SEQUENCE: - return "SEQUENCE"; - case TAG_NUMBER_SET: - return "SET"; - case TAG_NUMBER_BOOLEAN: - return "BOOLEAN"; - case TAG_NUMBER_GENERALIZED_TIME: - return "GENERALIZED TIME"; - case TAG_NUMBER_UTC_TIME: - return "UTC TIME"; - default: - return "0x" + Integer.toHexString(tagNumber); - } - } - - /** - * Returns {@code true} if the provided first identifier byte indicates that the data value uses - * constructed encoding for its contents, or {@code false} if the data value uses primitive - * encoding for its contents. - */ - public static boolean isConstructed(byte firstIdentifierByte) { - return (firstIdentifierByte & ID_FLAG_CONSTRUCTED_ENCODING) != 0; - } - - /** - * Returns the tag class encoded in the provided first identifier byte. See {@code TAG_CLASS} - * constants. - */ - public static int getTagClass(byte firstIdentifierByte) { - return (firstIdentifierByte & 0xff) >> 6; - } - - public static byte setTagClass(byte firstIdentifierByte, int tagClass) { - return (byte) ((firstIdentifierByte & 0x3f) | (tagClass << 6)); - } - - /** - * Returns the tag number encoded in the provided first identifier byte. See {@code TAG_NUMBER} - * constants. - */ - public static int getTagNumber(byte firstIdentifierByte) { - return firstIdentifierByte & 0x1f; - } - - public static byte setTagNumber(byte firstIdentifierByte, int tagNumber) { - return (byte) ((firstIdentifierByte & ~0x1f) | tagNumber); - } -} diff --git a/app/src/main/java/com/android/apksig/internal/asn1/ber/ByteBufferBerDataValueReader.java b/app/src/main/java/com/android/apksig/internal/asn1/ber/ByteBufferBerDataValueReader.java deleted file mode 100644 index 3fd5291f06..0000000000 --- a/app/src/main/java/com/android/apksig/internal/asn1/ber/ByteBufferBerDataValueReader.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.asn1.ber; - -import java.nio.ByteBuffer; - -/** - * {@link BerDataValueReader} which reads from a {@link ByteBuffer} containing BER-encoded data - * values. See {@code X.690} for the encoding. - */ -public class ByteBufferBerDataValueReader implements BerDataValueReader { - private final ByteBuffer mBuf; - - public ByteBufferBerDataValueReader(ByteBuffer buf) { - if (buf == null) { - throw new NullPointerException("buf == null"); - } - mBuf = buf; - } - - @Override - public BerDataValue readDataValue() throws BerDataValueFormatException { - int startPosition = mBuf.position(); - if (!mBuf.hasRemaining()) { - return null; - } - byte firstIdentifierByte = mBuf.get(); - int tagNumber = readTagNumber(firstIdentifierByte); - boolean constructed = BerEncoding.isConstructed(firstIdentifierByte); - - if (!mBuf.hasRemaining()) { - throw new BerDataValueFormatException("Missing length"); - } - int firstLengthByte = mBuf.get() & 0xff; - int contentsLength; - int contentsOffsetInTag; - if ((firstLengthByte & 0x80) == 0) { - // short form length - contentsLength = readShortFormLength(firstLengthByte); - contentsOffsetInTag = mBuf.position() - startPosition; - skipDefiniteLengthContents(contentsLength); - } else if (firstLengthByte != 0x80) { - // long form length - contentsLength = readLongFormLength(firstLengthByte); - contentsOffsetInTag = mBuf.position() - startPosition; - skipDefiniteLengthContents(contentsLength); - } else { - // indefinite length -- value ends with 0x00 0x00 - contentsOffsetInTag = mBuf.position() - startPosition; - contentsLength = - constructed - ? skipConstructedIndefiniteLengthContents() - : skipPrimitiveIndefiniteLengthContents(); - } - - // Create the encoded data value ByteBuffer - int endPosition = mBuf.position(); - mBuf.position(startPosition); - int bufOriginalLimit = mBuf.limit(); - mBuf.limit(endPosition); - ByteBuffer encoded = mBuf.slice(); - mBuf.position(mBuf.limit()); - mBuf.limit(bufOriginalLimit); - - // Create the encoded contents ByteBuffer - encoded.position(contentsOffsetInTag); - encoded.limit(contentsOffsetInTag + contentsLength); - ByteBuffer encodedContents = encoded.slice(); - encoded.clear(); - - return new BerDataValue( - encoded, - encodedContents, - BerEncoding.getTagClass(firstIdentifierByte), - constructed, - tagNumber); - } - - private int readTagNumber(byte firstIdentifierByte) throws BerDataValueFormatException { - int tagNumber = BerEncoding.getTagNumber(firstIdentifierByte); - if (tagNumber == 0x1f) { - // high-tag-number form, where the tag number follows this byte in base-128 - // big-endian form, where each byte has the highest bit set, except for the last - // byte - return readHighTagNumber(); - } else { - // low-tag-number form - return tagNumber; - } - } - - private int readHighTagNumber() throws BerDataValueFormatException { - // Base-128 big-endian form, where each byte has the highest bit set, except for the last - // byte - int b; - int result = 0; - do { - if (!mBuf.hasRemaining()) { - throw new BerDataValueFormatException("Truncated tag number"); - } - b = mBuf.get(); - if (result > Integer.MAX_VALUE >>> 7) { - throw new BerDataValueFormatException("Tag number too large"); - } - result <<= 7; - result |= b & 0x7f; - } while ((b & 0x80) != 0); - return result; - } - - private int readShortFormLength(int firstLengthByte) { - return firstLengthByte & 0x7f; - } - - private int readLongFormLength(int firstLengthByte) throws BerDataValueFormatException { - // The low 7 bits of the first byte represent the number of bytes (following the first - // byte) in which the length is in big-endian base-256 form - int byteCount = firstLengthByte & 0x7f; - if (byteCount > 4) { - throw new BerDataValueFormatException("Length too large: " + byteCount + " bytes"); - } - int result = 0; - for (int i = 0; i < byteCount; i++) { - if (!mBuf.hasRemaining()) { - throw new BerDataValueFormatException("Truncated length"); - } - int b = mBuf.get(); - if (result > Integer.MAX_VALUE >>> 8) { - throw new BerDataValueFormatException("Length too large"); - } - result <<= 8; - result |= b & 0xff; - } - return result; - } - - private void skipDefiniteLengthContents(int contentsLength) throws BerDataValueFormatException { - if (mBuf.remaining() < contentsLength) { - throw new BerDataValueFormatException( - "Truncated contents. Need: " + contentsLength + " bytes, available: " - + mBuf.remaining()); - } - mBuf.position(mBuf.position() + contentsLength); - } - - private int skipPrimitiveIndefiniteLengthContents() throws BerDataValueFormatException { - // Contents are terminated by 0x00 0x00 - boolean prevZeroByte = false; - int bytesRead = 0; - while (true) { - if (!mBuf.hasRemaining()) { - throw new BerDataValueFormatException( - "Truncated indefinite-length contents: " + bytesRead + " bytes read"); - - } - int b = mBuf.get(); - bytesRead++; - if (bytesRead < 0) { - throw new BerDataValueFormatException("Indefinite-length contents too long"); - } - if (b == 0) { - if (prevZeroByte) { - // End of contents reached -- we've read the value and its terminator 0x00 0x00 - return bytesRead - 2; - } - prevZeroByte = true; - } else { - prevZeroByte = false; - } - } - } - - private int skipConstructedIndefiniteLengthContents() throws BerDataValueFormatException { - // Contents are terminated by 0x00 0x00. However, this data value is constructed, meaning it - // can contain data values which are themselves indefinite length encoded. As a result, we - // must parse the direct children of this data value to correctly skip over the contents of - // this data value. - int startPos = mBuf.position(); - while (mBuf.hasRemaining()) { - // Check whether the 0x00 0x00 terminator is at current position - if ((mBuf.remaining() > 1) && (mBuf.getShort(mBuf.position()) == 0)) { - int contentsLength = mBuf.position() - startPos; - mBuf.position(mBuf.position() + 2); - return contentsLength; - } - // No luck. This must be a BER-encoded data value -- skip over it by parsing it - readDataValue(); - } - - throw new BerDataValueFormatException( - "Truncated indefinite-length contents: " - + (mBuf.position() - startPos) + " bytes read"); - } -} diff --git a/app/src/main/java/com/android/apksig/internal/asn1/ber/InputStreamBerDataValueReader.java b/app/src/main/java/com/android/apksig/internal/asn1/ber/InputStreamBerDataValueReader.java deleted file mode 100644 index 5fbca51db3..0000000000 --- a/app/src/main/java/com/android/apksig/internal/asn1/ber/InputStreamBerDataValueReader.java +++ /dev/null @@ -1,313 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.asn1.ber; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; - -/** - * {@link BerDataValueReader} which reads from an {@link InputStream} returning BER-encoded data - * values. See {@code X.690} for the encoding. - */ -public class InputStreamBerDataValueReader implements BerDataValueReader { - private final InputStream mIn; - - public InputStreamBerDataValueReader(InputStream in) { - if (in == null) { - throw new NullPointerException("in == null"); - } - mIn = in; - } - - @Override - public BerDataValue readDataValue() throws BerDataValueFormatException { - return readDataValue(mIn); - } - - /** - * Returns the next data value or {@code null} if end of input has been reached. - * - * @throws BerDataValueFormatException if the value being read is malformed. - */ - @SuppressWarnings("resource") - private static BerDataValue readDataValue(InputStream input) - throws BerDataValueFormatException { - RecordingInputStream in = new RecordingInputStream(input); - - try { - int firstIdentifierByte = in.read(); - if (firstIdentifierByte == -1) { - // End of input - return null; - } - int tagNumber = readTagNumber(in, firstIdentifierByte); - - int firstLengthByte = in.read(); - if (firstLengthByte == -1) { - throw new BerDataValueFormatException("Missing length"); - } - - boolean constructed = BerEncoding.isConstructed((byte) firstIdentifierByte); - int contentsLength; - int contentsOffsetInDataValue; - if ((firstLengthByte & 0x80) == 0) { - // short form length - contentsLength = readShortFormLength(firstLengthByte); - contentsOffsetInDataValue = in.getReadByteCount(); - skipDefiniteLengthContents(in, contentsLength); - } else if ((firstLengthByte & 0xff) != 0x80) { - // long form length - contentsLength = readLongFormLength(in, firstLengthByte); - contentsOffsetInDataValue = in.getReadByteCount(); - skipDefiniteLengthContents(in, contentsLength); - } else { - // indefinite length - contentsOffsetInDataValue = in.getReadByteCount(); - contentsLength = - constructed - ? skipConstructedIndefiniteLengthContents(in) - : skipPrimitiveIndefiniteLengthContents(in); - } - - byte[] encoded = in.getReadBytes(); - ByteBuffer encodedContents = - ByteBuffer.wrap(encoded, contentsOffsetInDataValue, contentsLength); - return new BerDataValue( - ByteBuffer.wrap(encoded), - encodedContents, - BerEncoding.getTagClass((byte) firstIdentifierByte), - constructed, - tagNumber); - } catch (IOException e) { - throw new BerDataValueFormatException("Failed to read data value", e); - } - } - - private static int readTagNumber(InputStream in, int firstIdentifierByte) - throws IOException, BerDataValueFormatException { - int tagNumber = BerEncoding.getTagNumber((byte) firstIdentifierByte); - if (tagNumber == 0x1f) { - // high-tag-number form - return readHighTagNumber(in); - } else { - // low-tag-number form - return tagNumber; - } - } - - private static int readHighTagNumber(InputStream in) - throws IOException, BerDataValueFormatException { - // Base-128 big-endian form, where each byte has the highest bit set, except for the last - // byte where the highest bit is not set - int b; - int result = 0; - do { - b = in.read(); - if (b == -1) { - throw new BerDataValueFormatException("Truncated tag number"); - } - if (result > Integer.MAX_VALUE >>> 7) { - throw new BerDataValueFormatException("Tag number too large"); - } - result <<= 7; - result |= b & 0x7f; - } while ((b & 0x80) != 0); - return result; - } - - private static int readShortFormLength(int firstLengthByte) { - return firstLengthByte & 0x7f; - } - - private static int readLongFormLength(InputStream in, int firstLengthByte) - throws IOException, BerDataValueFormatException { - // The low 7 bits of the first byte represent the number of bytes (following the first - // byte) in which the length is in big-endian base-256 form - int byteCount = firstLengthByte & 0x7f; - if (byteCount > 4) { - throw new BerDataValueFormatException("Length too large: " + byteCount + " bytes"); - } - int result = 0; - for (int i = 0; i < byteCount; i++) { - int b = in.read(); - if (b == -1) { - throw new BerDataValueFormatException("Truncated length"); - } - if (result > Integer.MAX_VALUE >>> 8) { - throw new BerDataValueFormatException("Length too large"); - } - result <<= 8; - result |= b & 0xff; - } - return result; - } - - private static void skipDefiniteLengthContents(InputStream in, int len) - throws IOException, BerDataValueFormatException { - long bytesRead = 0; - while (len > 0) { - int skipped = (int) in.skip(len); - if (skipped <= 0) { - throw new BerDataValueFormatException( - "Truncated definite-length contents: " + bytesRead + " bytes read" - + ", " + len + " missing"); - } - len -= skipped; - bytesRead += skipped; - } - } - - private static int skipPrimitiveIndefiniteLengthContents(InputStream in) - throws IOException, BerDataValueFormatException { - // Contents are terminated by 0x00 0x00 - boolean prevZeroByte = false; - int bytesRead = 0; - while (true) { - int b = in.read(); - if (b == -1) { - throw new BerDataValueFormatException( - "Truncated indefinite-length contents: " + bytesRead + " bytes read"); - } - bytesRead++; - if (bytesRead < 0) { - throw new BerDataValueFormatException("Indefinite-length contents too long"); - } - if (b == 0) { - if (prevZeroByte) { - // End of contents reached -- we've read the value and its terminator 0x00 0x00 - return bytesRead - 2; - } - prevZeroByte = true; - continue; - } else { - prevZeroByte = false; - } - } - } - - private static int skipConstructedIndefiniteLengthContents(RecordingInputStream in) - throws BerDataValueFormatException { - // Contents are terminated by 0x00 0x00. However, this data value is constructed, meaning it - // can contain data values which are indefinite length encoded as well. As a result, we - // must parse the direct children of this data value to correctly skip over the contents of - // this data value. - int readByteCountBefore = in.getReadByteCount(); - while (true) { - // We can't easily peek for the 0x00 0x00 terminator using the provided InputStream. - // Thus, we use the fact that 0x00 0x00 parses as a data value whose encoded form we - // then check below to see whether it's 0x00 0x00. - BerDataValue dataValue = readDataValue(in); - if (dataValue == null) { - throw new BerDataValueFormatException( - "Truncated indefinite-length contents: " - + (in.getReadByteCount() - readByteCountBefore) + " bytes read"); - } - if (in.getReadByteCount() <= 0) { - throw new BerDataValueFormatException("Indefinite-length contents too long"); - } - ByteBuffer encoded = dataValue.getEncoded(); - if ((encoded.remaining() == 2) && (encoded.get(0) == 0) && (encoded.get(1) == 0)) { - // 0x00 0x00 encountered - return in.getReadByteCount() - readByteCountBefore - 2; - } - } - } - - private static class RecordingInputStream extends InputStream { - private final InputStream mIn; - private final ByteArrayOutputStream mBuf; - - private RecordingInputStream(InputStream in) { - mIn = in; - mBuf = new ByteArrayOutputStream(); - } - - public byte[] getReadBytes() { - return mBuf.toByteArray(); - } - - public int getReadByteCount() { - return mBuf.size(); - } - - @Override - public int read() throws IOException { - int b = mIn.read(); - if (b != -1) { - mBuf.write(b); - } - return b; - } - - @Override - public int read(byte[] b) throws IOException { - int len = mIn.read(b); - if (len > 0) { - mBuf.write(b, 0, len); - } - return len; - } - - @Override - public int read(byte[] b, int off, int len) throws IOException { - len = mIn.read(b, off, len); - if (len > 0) { - mBuf.write(b, off, len); - } - return len; - } - - @Override - public long skip(long n) throws IOException { - if (n <= 0) { - return mIn.skip(n); - } - - byte[] buf = new byte[4096]; - int len = mIn.read(buf, 0, (int) Math.min(buf.length, n)); - if (len > 0) { - mBuf.write(buf, 0, len); - } - return (len < 0) ? 0 : len; - } - - @Override - public int available() throws IOException { - return super.available(); - } - - @Override - public void close() throws IOException { - super.close(); - } - - @Override - public synchronized void mark(int readlimit) {} - - @Override - public synchronized void reset() throws IOException { - throw new IOException("mark/reset not supported"); - } - - @Override - public boolean markSupported() { - return false; - } - } -} diff --git a/app/src/main/java/com/android/apksig/internal/jar/ManifestParser.java b/app/src/main/java/com/android/apksig/internal/jar/ManifestParser.java deleted file mode 100644 index ab0a5dad8a..0000000000 --- a/app/src/main/java/com/android/apksig/internal/jar/ManifestParser.java +++ /dev/null @@ -1,363 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.jar; - -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.jar.Attributes; - -/** - * JAR manifest and signature file parser. - * - *

These files consist of a main section followed by individual sections. Individual sections - * are named, their names referring to JAR entries. - * - * @see JAR Manifest format - */ -public class ManifestParser { - - private final byte[] mManifest; - private int mOffset; - private int mEndOffset; - - private byte[] 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

readAllSections() { - List
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 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) { - // Name is separated from value by a semicolon followed by a single SPACE character. - // This permits trailing spaces in names and leading and trailing spaces in values. - // Some APK obfuscators take advantage of this fact. We thus need to preserve these unusual - // spaces to be able to parse such obfuscated APKs. - int delimiterIndex = attr.indexOf(": "); - if (delimiterIndex == -1) { - return new Attribute(attr, ""); - } else { - return new Attribute( - attr.substring(0, delimiterIndex), - attr.substring(delimiterIndex + ": ".length())); - } - } - - /** - * 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() { - byte[] bytes = readAttributeBytes(); - if (bytes == null) { - return null; - } else if (bytes.length == 0) { - return ""; - } else { - return new String(bytes, StandardCharsets.UTF_8); - } - } - - /** - * Returns the next attribute or empty array if end of section has been reached or {@code null} - * if end of input has been reached. - */ - private byte[] readAttributeBytes() { - // Check whether end of section was reached during previous invocation - if ((mBufferedLine != null) && (mBufferedLine.length == 0)) { - mBufferedLine = null; - return EMPTY_BYTE_ARRAY; - } - - // Read the next line - byte[] line = readLine(); - if (line == null) { - // End of input - if (mBufferedLine != null) { - byte[] result = mBufferedLine; - mBufferedLine = null; - return result; - } - return null; - } - - // Consume the read line - if (line.length == 0) { - // End of section - if (mBufferedLine != null) { - byte[] result = mBufferedLine; - mBufferedLine = EMPTY_BYTE_ARRAY; - return result; - } - return EMPTY_BYTE_ARRAY; - } - byte[] attrLine; - if (mBufferedLine == null) { - attrLine = line; - } else { - if ((line.length == 0) || (line[0] != ' ')) { - // The most common case: buffered line is a full attribute - byte[] result = mBufferedLine; - mBufferedLine = line; - return result; - } - attrLine = mBufferedLine; - mBufferedLine = null; - attrLine = concat(attrLine, line, 1, line.length - 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; - } else if (line.length == 0) { - // End of section - mBufferedLine = EMPTY_BYTE_ARRAY; // return "end of section" next time - return attrLine; - } - if (line[0] == ' ') { - // Continuation line - attrLine = concat(attrLine, line, 1, line.length - 1); - } else { - // Next attribute - mBufferedLine = line; - return attrLine; - } - } - } - - private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; - - private static byte[] concat(byte[] arr1, byte[] arr2, int offset2, int length2) { - byte[] result = new byte[arr1.length + length2]; - System.arraycopy(arr1, 0, result, 0, arr1.length); - System.arraycopy(arr2, offset2, result, arr1.length, length2); - return result; - } - - /** - * Returns the next line (without line delimiter characters) or {@code null} if end of input has - * been reached. - */ - private byte[] 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; - - if (newlineStartOffset == startOffset) { - return EMPTY_BYTE_ARRAY; - } - return Arrays.copyOfRange(mManifest, startOffset, newlineStartOffset); - } - - - /** - * 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 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 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 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/app/src/main/java/com/android/apksig/internal/jar/ManifestWriter.java b/app/src/main/java/com/android/apksig/internal/jar/ManifestWriter.java deleted file mode 100644 index fa01beb7b7..0000000000 --- a/app/src/main/java/com/android/apksig/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.apksig.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 JAR Manifest format - */ -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 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 getAttributesSortedByName(Attributes attributes) { - Set> attributesEntries = attributes.entrySet(); - SortedMap namedAttributes = new TreeMap(); - for (Map.Entry attribute : attributesEntries) { - String attrName = attribute.getKey().toString(); - String attrValue = attribute.getValue().toString(); - namedAttributes.put(attrName, attrValue); - } - return namedAttributes; - } - - static void writeAttributes( - OutputStream out, SortedMap attributesSortedByName) throws IOException { - for (Map.Entry attribute : attributesSortedByName.entrySet()) { - String attrName = attribute.getKey(); - String attrValue = attribute.getValue(); - writeAttribute(out, attrName, attrValue); - } - } -} diff --git a/app/src/main/java/com/android/apksig/internal/jar/SignatureFileWriter.java b/app/src/main/java/com/android/apksig/internal/jar/SignatureFileWriter.java deleted file mode 100644 index fd8cbff8dc..0000000000 --- a/app/src/main/java/com/android/apksig/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.apksig.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 JAR Manifest format - */ -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 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/app/src/main/java/com/android/apksig/internal/oid/OidConstants.java b/app/src/main/java/com/android/apksig/internal/oid/OidConstants.java deleted file mode 100644 index d80cbaa6ee..0000000000 --- a/app/src/main/java/com/android/apksig/internal/oid/OidConstants.java +++ /dev/null @@ -1,463 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.oid; - -import com.android.apksig.internal.util.InclusiveIntRange; - -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class OidConstants { - public static final String OID_DIGEST_MD5 = "1.2.840.113549.2.5"; - public static final String OID_DIGEST_SHA1 = "1.3.14.3.2.26"; - public static final String OID_DIGEST_SHA224 = "2.16.840.1.101.3.4.2.4"; - public static final String OID_DIGEST_SHA256 = "2.16.840.1.101.3.4.2.1"; - public static final String OID_DIGEST_SHA384 = "2.16.840.1.101.3.4.2.2"; - public static final String OID_DIGEST_SHA512 = "2.16.840.1.101.3.4.2.3"; - - public static final String OID_SIG_RSA = "1.2.840.113549.1.1.1"; - public static final String OID_SIG_MD5_WITH_RSA = "1.2.840.113549.1.1.4"; - public static final String OID_SIG_SHA1_WITH_RSA = "1.2.840.113549.1.1.5"; - public static final String OID_SIG_SHA224_WITH_RSA = "1.2.840.113549.1.1.14"; - public static final String OID_SIG_SHA256_WITH_RSA = "1.2.840.113549.1.1.11"; - public static final String OID_SIG_SHA384_WITH_RSA = "1.2.840.113549.1.1.12"; - public static final String OID_SIG_SHA512_WITH_RSA = "1.2.840.113549.1.1.13"; - - public static final String OID_SIG_DSA = "1.2.840.10040.4.1"; - public static final String OID_SIG_SHA1_WITH_DSA = "1.2.840.10040.4.3"; - public static final String OID_SIG_SHA224_WITH_DSA = "2.16.840.1.101.3.4.3.1"; - public static final String OID_SIG_SHA256_WITH_DSA = "2.16.840.1.101.3.4.3.2"; - public static final String OID_SIG_SHA384_WITH_DSA = "2.16.840.1.101.3.4.3.3"; - public static final String OID_SIG_SHA512_WITH_DSA = "2.16.840.1.101.3.4.3.4"; - - public static final String OID_SIG_EC_PUBLIC_KEY = "1.2.840.10045.2.1"; - public static final String OID_SIG_SHA1_WITH_ECDSA = "1.2.840.10045.4.1"; - public static final String OID_SIG_SHA224_WITH_ECDSA = "1.2.840.10045.4.3.1"; - public static final String OID_SIG_SHA256_WITH_ECDSA = "1.2.840.10045.4.3.2"; - public static final String OID_SIG_SHA384_WITH_ECDSA = "1.2.840.10045.4.3.3"; - public static final String OID_SIG_SHA512_WITH_ECDSA = "1.2.840.10045.4.3.4"; - - public static final Map> SUPPORTED_SIG_ALG_OIDS = - new HashMap<>(); - static { - 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_SHA1, OID_SIG_EC_PUBLIC_KEY, - InclusiveIntRange.from(18)); - addSupportedSigAlg( - OID_DIGEST_SHA224, OID_SIG_EC_PUBLIC_KEY, - InclusiveIntRange.from(21)); - addSupportedSigAlg( - OID_DIGEST_SHA256, OID_SIG_EC_PUBLIC_KEY, - InclusiveIntRange.from(18)); - addSupportedSigAlg( - OID_DIGEST_SHA384, OID_SIG_EC_PUBLIC_KEY, - InclusiveIntRange.from(18)); - addSupportedSigAlg( - OID_DIGEST_SHA512, OID_SIG_EC_PUBLIC_KEY, - InclusiveIntRange.from(18)); - - 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)); - } - - public static void addSupportedSigAlg( - String digestAlgorithmOid, - String signatureAlgorithmOid, - InclusiveIntRange... supportedApiLevels) { - SUPPORTED_SIG_ALG_OIDS.put( - digestAlgorithmOid + "with" + signatureAlgorithmOid, - Arrays.asList(supportedApiLevels)); - } - - public static List getSigAlgSupportedApiLevels( - String digestAlgorithmOid, - String signatureAlgorithmOid) { - List result = - SUPPORTED_SIG_ALG_OIDS.get(digestAlgorithmOid + "with" + signatureAlgorithmOid); - return (result != null) ? result : Collections.emptyList(); - } - - public static class OidToUserFriendlyNameMapper { - private OidToUserFriendlyNameMapper() {} - - private static final Map OID_TO_USER_FRIENDLY_NAME = new HashMap<>(); - static { - OID_TO_USER_FRIENDLY_NAME.put(OID_DIGEST_MD5, "MD5"); - OID_TO_USER_FRIENDLY_NAME.put(OID_DIGEST_SHA1, "SHA-1"); - OID_TO_USER_FRIENDLY_NAME.put(OID_DIGEST_SHA224, "SHA-224"); - OID_TO_USER_FRIENDLY_NAME.put(OID_DIGEST_SHA256, "SHA-256"); - OID_TO_USER_FRIENDLY_NAME.put(OID_DIGEST_SHA384, "SHA-384"); - OID_TO_USER_FRIENDLY_NAME.put(OID_DIGEST_SHA512, "SHA-512"); - - OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_RSA, "RSA"); - OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_MD5_WITH_RSA, "MD5 with RSA"); - OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA1_WITH_RSA, "SHA-1 with RSA"); - OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA224_WITH_RSA, "SHA-224 with RSA"); - OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA256_WITH_RSA, "SHA-256 with RSA"); - OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA384_WITH_RSA, "SHA-384 with RSA"); - OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA512_WITH_RSA, "SHA-512 with RSA"); - - - OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_DSA, "DSA"); - OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA1_WITH_DSA, "SHA-1 with DSA"); - OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA224_WITH_DSA, "SHA-224 with DSA"); - OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA256_WITH_DSA, "SHA-256 with DSA"); - OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA384_WITH_DSA, "SHA-384 with DSA"); - OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA512_WITH_DSA, "SHA-512 with DSA"); - - OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_EC_PUBLIC_KEY, "ECDSA"); - OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA1_WITH_ECDSA, "SHA-1 with ECDSA"); - OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA224_WITH_ECDSA, "SHA-224 with ECDSA"); - OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA256_WITH_ECDSA, "SHA-256 with ECDSA"); - OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA384_WITH_ECDSA, "SHA-384 with ECDSA"); - OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA512_WITH_ECDSA, "SHA-512 with ECDSA"); - } - - public static String getUserFriendlyNameForOid(String oid) { - return OID_TO_USER_FRIENDLY_NAME.get(oid); - } - } - - public static final Map OID_TO_JCA_DIGEST_ALG = new HashMap<>(); - static { - OID_TO_JCA_DIGEST_ALG.put(OID_DIGEST_MD5, "MD5"); - OID_TO_JCA_DIGEST_ALG.put(OID_DIGEST_SHA1, "SHA-1"); - OID_TO_JCA_DIGEST_ALG.put(OID_DIGEST_SHA224, "SHA-224"); - OID_TO_JCA_DIGEST_ALG.put(OID_DIGEST_SHA256, "SHA-256"); - OID_TO_JCA_DIGEST_ALG.put(OID_DIGEST_SHA384, "SHA-384"); - OID_TO_JCA_DIGEST_ALG.put(OID_DIGEST_SHA512, "SHA-512"); - } - - public static final Map OID_TO_JCA_SIGNATURE_ALG = new HashMap<>(); - static { - OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_MD5_WITH_RSA, "MD5withRSA"); - OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA1_WITH_RSA, "SHA1withRSA"); - OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA224_WITH_RSA, "SHA224withRSA"); - OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA256_WITH_RSA, "SHA256withRSA"); - OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA384_WITH_RSA, "SHA384withRSA"); - OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA512_WITH_RSA, "SHA512withRSA"); - - OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA1_WITH_DSA, "SHA1withDSA"); - OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA224_WITH_DSA, "SHA224withDSA"); - OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA256_WITH_DSA, "SHA256withDSA"); - - OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA1_WITH_ECDSA, "SHA1withECDSA"); - OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA224_WITH_ECDSA, "SHA224withECDSA"); - OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA256_WITH_ECDSA, "SHA256withECDSA"); - OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA384_WITH_ECDSA, "SHA384withECDSA"); - OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA512_WITH_ECDSA, "SHA512withECDSA"); - } - - private OidConstants() {} -} diff --git a/app/src/main/java/com/android/apksig/internal/pkcs7/AlgorithmIdentifier.java b/app/src/main/java/com/android/apksig/internal/pkcs7/AlgorithmIdentifier.java deleted file mode 100644 index 9712767293..0000000000 --- a/app/src/main/java/com/android/apksig/internal/pkcs7/AlgorithmIdentifier.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.pkcs7; - -import static com.android.apksig.Constants.OID_RSA_ENCRYPTION; -import static com.android.apksig.internal.asn1.Asn1DerEncoder.ASN1_DER_NULL; -import static com.android.apksig.internal.oid.OidConstants.OID_DIGEST_SHA1; -import static com.android.apksig.internal.oid.OidConstants.OID_DIGEST_SHA256; -import static com.android.apksig.internal.oid.OidConstants.OID_SIG_DSA; -import static com.android.apksig.internal.oid.OidConstants.OID_SIG_EC_PUBLIC_KEY; -import static com.android.apksig.internal.oid.OidConstants.OID_SIG_RSA; -import static com.android.apksig.internal.oid.OidConstants.OID_SIG_SHA256_WITH_DSA; -import static com.android.apksig.internal.oid.OidConstants.OID_TO_JCA_DIGEST_ALG; -import static com.android.apksig.internal.oid.OidConstants.OID_TO_JCA_SIGNATURE_ALG; - -import com.android.apksig.internal.apk.v1.DigestAlgorithm; -import com.android.apksig.internal.asn1.Asn1Class; -import com.android.apksig.internal.asn1.Asn1Field; -import com.android.apksig.internal.asn1.Asn1OpaqueObject; -import com.android.apksig.internal.asn1.Asn1Type; -import com.android.apksig.internal.util.Pair; - -import java.security.InvalidKeyException; -import java.security.PublicKey; -import java.security.Signature; -import java.security.SignatureException; - -/** - * PKCS #7 {@code AlgorithmIdentifier} as specified in RFC 5652. - */ -@Asn1Class(type = Asn1Type.SEQUENCE) -public class AlgorithmIdentifier { - - @Asn1Field(index = 0, type = Asn1Type.OBJECT_IDENTIFIER) - public String algorithm; - - @Asn1Field(index = 1, type = Asn1Type.ANY, optional = true) - public Asn1OpaqueObject parameters; - - public AlgorithmIdentifier() {} - - public AlgorithmIdentifier(String algorithmOid, Asn1OpaqueObject parameters) { - this.algorithm = algorithmOid; - this.parameters = parameters; - } - - /** - * Returns the PKCS #7 {@code DigestAlgorithm} to use when signing using the specified digest - * algorithm. - */ - public static AlgorithmIdentifier getSignerInfoDigestAlgorithmOid( - DigestAlgorithm digestAlgorithm) { - switch (digestAlgorithm) { - case SHA1: - return new AlgorithmIdentifier(OID_DIGEST_SHA1, ASN1_DER_NULL); - case SHA256: - return new AlgorithmIdentifier(OID_DIGEST_SHA256, ASN1_DER_NULL); - } - throw new IllegalArgumentException("Unsupported digest algorithm: " + digestAlgorithm); - } - - /** - * Returns the JCA {@link Signature} algorithm and PKCS #7 {@code SignatureAlgorithm} to use - * when signing with the specified key and digest algorithm. - */ - public static Pair getSignerInfoSignatureAlgorithm( - PublicKey publicKey, DigestAlgorithm digestAlgorithm, boolean deterministicDsaSigning) - throws InvalidKeyException { - String keyAlgorithm = publicKey.getAlgorithm(); - String jcaDigestPrefixForSigAlg; - switch (digestAlgorithm) { - case SHA1: - jcaDigestPrefixForSigAlg = "SHA1"; - break; - case SHA256: - jcaDigestPrefixForSigAlg = "SHA256"; - break; - default: - throw new IllegalArgumentException( - "Unexpected digest algorithm: " + digestAlgorithm); - } - if ("RSA".equalsIgnoreCase(keyAlgorithm) || OID_RSA_ENCRYPTION.equals(keyAlgorithm)) { - return Pair.of( - jcaDigestPrefixForSigAlg + "withRSA", - new AlgorithmIdentifier(OID_SIG_RSA, ASN1_DER_NULL)); - } else if ("DSA".equalsIgnoreCase(keyAlgorithm)) { - AlgorithmIdentifier sigAlgId; - switch (digestAlgorithm) { - case SHA1: - sigAlgId = - new AlgorithmIdentifier(OID_SIG_DSA, ASN1_DER_NULL); - 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 = - new AlgorithmIdentifier(OID_SIG_SHA256_WITH_DSA, ASN1_DER_NULL); - break; - default: - throw new IllegalArgumentException( - "Unexpected digest algorithm: " + digestAlgorithm); - } - String signingAlgorithmName = - jcaDigestPrefixForSigAlg + (deterministicDsaSigning ? "withDetDSA" : "withDSA"); - return Pair.of(signingAlgorithmName, sigAlgId); - } else if ("EC".equalsIgnoreCase(keyAlgorithm)) { - return Pair.of( - jcaDigestPrefixForSigAlg + "withECDSA", - new AlgorithmIdentifier(OID_SIG_EC_PUBLIC_KEY, ASN1_DER_NULL)); - } else { - throw new InvalidKeyException("Unsupported key algorithm: " + keyAlgorithm); - } - } - - public static String getJcaSignatureAlgorithm( - String digestAlgorithmOid, - String signatureAlgorithmOid) throws SignatureException { - // First check whether the signature algorithm OID alone is sufficient - String result = OID_TO_JCA_SIGNATURE_ALG.get(signatureAlgorithmOid); - if (result != null) { - return result; - } - - // Signature algorithm OID alone is insufficient. Need to combine digest algorithm OID - // with signature algorithm OID. - String suffix; - if (OID_SIG_RSA.equals(signatureAlgorithmOid)) { - suffix = "RSA"; - } else if (OID_SIG_DSA.equals(signatureAlgorithmOid)) { - suffix = "DSA"; - } else if (OID_SIG_EC_PUBLIC_KEY.equals(signatureAlgorithmOid)) { - suffix = "ECDSA"; - } else { - throw new SignatureException( - "Unsupported JCA Signature algorithm" - + " . Digest algorithm: " + digestAlgorithmOid - + ", signature algorithm: " + signatureAlgorithmOid); - } - String jcaDigestAlg = getJcaDigestAlgorithm(digestAlgorithmOid); - // Canonical name for SHA-1 with ... is SHA1with, rather than SHA1. Same for all other - // SHA algorithms. - if (jcaDigestAlg.startsWith("SHA-")) { - jcaDigestAlg = "SHA" + jcaDigestAlg.substring("SHA-".length()); - } - return jcaDigestAlg + "with" + suffix; - } - - public static String getJcaDigestAlgorithm(String oid) - throws SignatureException { - String result = OID_TO_JCA_DIGEST_ALG.get(oid); - if (result == null) { - throw new SignatureException("Unsupported digest algorithm: " + oid); - } - return result; - } -} diff --git a/app/src/main/java/com/android/apksig/internal/pkcs7/Attribute.java b/app/src/main/java/com/android/apksig/internal/pkcs7/Attribute.java deleted file mode 100644 index a6c91efac6..0000000000 --- a/app/src/main/java/com/android/apksig/internal/pkcs7/Attribute.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.pkcs7; - -import com.android.apksig.internal.asn1.Asn1Class; -import com.android.apksig.internal.asn1.Asn1Field; -import com.android.apksig.internal.asn1.Asn1OpaqueObject; -import com.android.apksig.internal.asn1.Asn1Type; -import java.util.List; - -/** - * PKCS #7 {@code Attribute} as specified in RFC 5652. - */ -@Asn1Class(type = Asn1Type.SEQUENCE) -public class Attribute { - - @Asn1Field(index = 0, type = Asn1Type.OBJECT_IDENTIFIER) - public String attrType; - - @Asn1Field(index = 1, type = Asn1Type.SET_OF) - public List attrValues; -} diff --git a/app/src/main/java/com/android/apksig/internal/pkcs7/ContentInfo.java b/app/src/main/java/com/android/apksig/internal/pkcs7/ContentInfo.java deleted file mode 100644 index 8ab722c2db..0000000000 --- a/app/src/main/java/com/android/apksig/internal/pkcs7/ContentInfo.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.pkcs7; - -import com.android.apksig.internal.asn1.Asn1Class; -import com.android.apksig.internal.asn1.Asn1Field; -import com.android.apksig.internal.asn1.Asn1OpaqueObject; -import com.android.apksig.internal.asn1.Asn1Type; -import com.android.apksig.internal.asn1.Asn1Tagging; - -/** - * PKCS #7 {@code ContentInfo} as specified in RFC 5652. - */ -@Asn1Class(type = Asn1Type.SEQUENCE) -public class ContentInfo { - - @Asn1Field(index = 1, type = Asn1Type.OBJECT_IDENTIFIER) - public String contentType; - - @Asn1Field(index = 2, type = Asn1Type.ANY, tagging = Asn1Tagging.EXPLICIT, tagNumber = 0) - public Asn1OpaqueObject content; -} diff --git a/app/src/main/java/com/android/apksig/internal/pkcs7/EncapsulatedContentInfo.java b/app/src/main/java/com/android/apksig/internal/pkcs7/EncapsulatedContentInfo.java deleted file mode 100644 index 79f41af89d..0000000000 --- a/app/src/main/java/com/android/apksig/internal/pkcs7/EncapsulatedContentInfo.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.pkcs7; - -import com.android.apksig.internal.asn1.Asn1Class; -import com.android.apksig.internal.asn1.Asn1Field; -import com.android.apksig.internal.asn1.Asn1Type; -import com.android.apksig.internal.asn1.Asn1Tagging; -import java.nio.ByteBuffer; - -/** - * PKCS #7 {@code EncapsulatedContentInfo} as specified in RFC 5652. - */ -@Asn1Class(type = Asn1Type.SEQUENCE) -public class EncapsulatedContentInfo { - - @Asn1Field(index = 0, type = Asn1Type.OBJECT_IDENTIFIER) - public String contentType; - - @Asn1Field( - index = 1, - type = Asn1Type.OCTET_STRING, - tagging = Asn1Tagging.EXPLICIT, tagNumber = 0, - optional = true) - public ByteBuffer content; - - public EncapsulatedContentInfo() {} - - public EncapsulatedContentInfo(String contentTypeOid) { - contentType = contentTypeOid; - } -} diff --git a/app/src/main/java/com/android/apksig/internal/pkcs7/IssuerAndSerialNumber.java b/app/src/main/java/com/android/apksig/internal/pkcs7/IssuerAndSerialNumber.java deleted file mode 100644 index 284b11764b..0000000000 --- a/app/src/main/java/com/android/apksig/internal/pkcs7/IssuerAndSerialNumber.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.pkcs7; - -import com.android.apksig.internal.asn1.Asn1Class; -import com.android.apksig.internal.asn1.Asn1Field; -import com.android.apksig.internal.asn1.Asn1OpaqueObject; -import com.android.apksig.internal.asn1.Asn1Type; -import java.math.BigInteger; - -/** - * PKCS #7 {@code IssuerAndSerialNumber} as specified in RFC 5652. - */ -@Asn1Class(type = Asn1Type.SEQUENCE) -public class IssuerAndSerialNumber { - - @Asn1Field(index = 0, type = Asn1Type.ANY) - public Asn1OpaqueObject issuer; - - @Asn1Field(index = 1, type = Asn1Type.INTEGER) - public BigInteger certificateSerialNumber; - - public IssuerAndSerialNumber() {} - - public IssuerAndSerialNumber(Asn1OpaqueObject issuer, BigInteger certificateSerialNumber) { - this.issuer = issuer; - this.certificateSerialNumber = certificateSerialNumber; - } -} diff --git a/app/src/main/java/com/android/apksig/internal/pkcs7/Pkcs7Constants.java b/app/src/main/java/com/android/apksig/internal/pkcs7/Pkcs7Constants.java deleted file mode 100644 index 1a115d5156..0000000000 --- a/app/src/main/java/com/android/apksig/internal/pkcs7/Pkcs7Constants.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.pkcs7; - -/** - * Assorted PKCS #7 constants from RFC 5652. - */ -public abstract class Pkcs7Constants { - private Pkcs7Constants() {} - - public static final String OID_DATA = "1.2.840.113549.1.7.1"; - public static final String OID_SIGNED_DATA = "1.2.840.113549.1.7.2"; - public static final String OID_CONTENT_TYPE = "1.2.840.113549.1.9.3"; - public static final String OID_MESSAGE_DIGEST = "1.2.840.113549.1.9.4"; -} diff --git a/app/src/main/java/com/android/apksig/internal/pkcs7/Pkcs7DecodingException.java b/app/src/main/java/com/android/apksig/internal/pkcs7/Pkcs7DecodingException.java deleted file mode 100644 index 4004ee7f78..0000000000 --- a/app/src/main/java/com/android/apksig/internal/pkcs7/Pkcs7DecodingException.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.pkcs7; - -/** - * Indicates that an error was encountered while decoding a PKCS #7 structure. - */ -public class Pkcs7DecodingException extends Exception { - private static final long serialVersionUID = 1L; - - public Pkcs7DecodingException(String message) { - super(message); - } - - public Pkcs7DecodingException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/app/src/main/java/com/android/apksig/internal/pkcs7/SignedData.java b/app/src/main/java/com/android/apksig/internal/pkcs7/SignedData.java deleted file mode 100644 index 56b6e502dc..0000000000 --- a/app/src/main/java/com/android/apksig/internal/pkcs7/SignedData.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.pkcs7; - -import com.android.apksig.internal.asn1.Asn1Class; -import com.android.apksig.internal.asn1.Asn1Field; -import com.android.apksig.internal.asn1.Asn1OpaqueObject; -import com.android.apksig.internal.asn1.Asn1Type; -import com.android.apksig.internal.asn1.Asn1Tagging; -import java.nio.ByteBuffer; -import java.util.List; - -/** - * PKCS #7 {@code SignedData} as specified in RFC 5652. - */ -@Asn1Class(type = Asn1Type.SEQUENCE) -public class SignedData { - - @Asn1Field(index = 0, type = Asn1Type.INTEGER) - public int version; - - @Asn1Field(index = 1, type = Asn1Type.SET_OF) - public List digestAlgorithms; - - @Asn1Field(index = 2, type = Asn1Type.SEQUENCE) - public EncapsulatedContentInfo encapContentInfo; - - @Asn1Field( - index = 3, - type = Asn1Type.SET_OF, - tagging = Asn1Tagging.IMPLICIT, tagNumber = 0, - optional = true) - public List certificates; - - @Asn1Field( - index = 4, - type = Asn1Type.SET_OF, - tagging = Asn1Tagging.IMPLICIT, tagNumber = 1, - optional = true) - public List crls; - - @Asn1Field(index = 5, type = Asn1Type.SET_OF) - public List signerInfos; -} diff --git a/app/src/main/java/com/android/apksig/internal/pkcs7/SignerIdentifier.java b/app/src/main/java/com/android/apksig/internal/pkcs7/SignerIdentifier.java deleted file mode 100644 index a3d70f16bb..0000000000 --- a/app/src/main/java/com/android/apksig/internal/pkcs7/SignerIdentifier.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.pkcs7; - -import com.android.apksig.internal.asn1.Asn1Class; -import com.android.apksig.internal.asn1.Asn1Field; -import com.android.apksig.internal.asn1.Asn1Type; -import com.android.apksig.internal.asn1.Asn1Tagging; -import java.nio.ByteBuffer; - -/** - * PKCS #7 {@code SignerIdentifier} as specified in RFC 5652. - */ -@Asn1Class(type = Asn1Type.CHOICE) -public class SignerIdentifier { - - @Asn1Field(type = Asn1Type.SEQUENCE) - public IssuerAndSerialNumber issuerAndSerialNumber; - - @Asn1Field(type = Asn1Type.OCTET_STRING, tagging = Asn1Tagging.IMPLICIT, tagNumber = 0) - public ByteBuffer subjectKeyIdentifier; - - public SignerIdentifier() {} - - public SignerIdentifier(IssuerAndSerialNumber issuerAndSerialNumber) { - this.issuerAndSerialNumber = issuerAndSerialNumber; - } -} diff --git a/app/src/main/java/com/android/apksig/internal/pkcs7/SignerInfo.java b/app/src/main/java/com/android/apksig/internal/pkcs7/SignerInfo.java deleted file mode 100644 index b885eb8002..0000000000 --- a/app/src/main/java/com/android/apksig/internal/pkcs7/SignerInfo.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.pkcs7; - -import com.android.apksig.internal.asn1.Asn1Class; -import com.android.apksig.internal.asn1.Asn1Field; -import com.android.apksig.internal.asn1.Asn1OpaqueObject; -import com.android.apksig.internal.asn1.Asn1Type; -import com.android.apksig.internal.asn1.Asn1Tagging; -import java.nio.ByteBuffer; -import java.util.List; - -/** - * PKCS #7 {@code SignerInfo} as specified in RFC 5652. - */ -@Asn1Class(type = Asn1Type.SEQUENCE) -public class SignerInfo { - - @Asn1Field(index = 0, type = Asn1Type.INTEGER) - public int version; - - @Asn1Field(index = 1, type = Asn1Type.CHOICE) - public SignerIdentifier sid; - - @Asn1Field(index = 2, type = Asn1Type.SEQUENCE) - public AlgorithmIdentifier digestAlgorithm; - - @Asn1Field( - index = 3, - type = Asn1Type.SET_OF, - tagging = Asn1Tagging.IMPLICIT, tagNumber = 0, - optional = true) - public Asn1OpaqueObject signedAttrs; - - @Asn1Field(index = 4, type = Asn1Type.SEQUENCE) - public AlgorithmIdentifier signatureAlgorithm; - - @Asn1Field(index = 5, type = Asn1Type.OCTET_STRING) - public ByteBuffer signature; - - @Asn1Field( - index = 6, - type = Asn1Type.SET_OF, - tagging = Asn1Tagging.IMPLICIT, tagNumber = 1, - optional = true) - public List unsignedAttrs; -} diff --git a/app/src/main/java/com/android/apksig/internal/util/AndroidSdkVersion.java b/app/src/main/java/com/android/apksig/internal/util/AndroidSdkVersion.java deleted file mode 100644 index 87eae48445..0000000000 --- a/app/src/main/java/com/android/apksig/internal/util/AndroidSdkVersion.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.util; - -/** - * Android SDK version / API Level constants. - */ -public abstract class AndroidSdkVersion { - - /** Hidden constructor to prevent instantiation. */ - private AndroidSdkVersion() {} - - /** Android 1.0 */ - public static final int INITIAL_RELEASE = 1; - - /** Android 2.3. */ - public static final int GINGERBREAD = 9; - - /** Android 3.0 */ - public static final int HONEYCOMB = 11; - - /** Android 4.3. The revenge of the beans. */ - public static final int JELLY_BEAN_MR2 = 18; - - /** Android 4.4. KitKat, another tasty treat. */ - public static final int KITKAT = 19; - - /** Android 5.0. A flat one with beautiful shadows. But still tasty. */ - public static final int LOLLIPOP = 21; - - /** Android 6.0. M is for Marshmallow! */ - public static final int M = 23; - - /** Android 7.0. N is for Nougat. */ - public static final int N = 24; - - /** Android O. */ - public static final int O = 26; - - /** Android P. */ - public static final int P = 28; - - /** Android R. */ - public static final int R = 30; -} diff --git a/app/src/main/java/com/android/apksig/internal/util/ByteArrayDataSink.java b/app/src/main/java/com/android/apksig/internal/util/ByteArrayDataSink.java deleted file mode 100644 index e5741a5b53..0000000000 --- a/app/src/main/java/com/android/apksig/internal/util/ByteArrayDataSink.java +++ /dev/null @@ -1,240 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.util; - -import com.android.apksig.util.DataSink; -import com.android.apksig.util.DataSource; -import com.android.apksig.util.ReadableDataSink; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.Arrays; - -/** - * Growable byte array which can be appended to via {@link DataSink} interface and read from via - * {@link DataSource} interface. - */ -public class ByteArrayDataSink implements ReadableDataSink { - - private static final int MAX_READ_CHUNK_SIZE = 65536; - - private byte[] mArray; - private int mSize; - - public ByteArrayDataSink() { - this(65536); - } - - public ByteArrayDataSink(int initialCapacity) { - if (initialCapacity < 0) { - throw new IllegalArgumentException("initial capacity: " + initialCapacity); - } - mArray = new byte[initialCapacity]; - } - - @Override - public void consume(byte[] buf, int offset, int length) throws IOException { - if (offset < 0) { - // Must perform this check because System.arraycopy below doesn't perform it when - // length == 0 - throw new IndexOutOfBoundsException("offset: " + offset); - } - if (offset > buf.length) { - // Must perform this check because System.arraycopy below doesn't perform it when - // length == 0 - throw new IndexOutOfBoundsException( - "offset: " + offset + ", buf.length: " + buf.length); - } - if (length == 0) { - return; - } - - ensureAvailable(length); - System.arraycopy(buf, offset, mArray, mSize, length); - mSize += length; - } - - @Override - public void consume(ByteBuffer buf) throws IOException { - if (!buf.hasRemaining()) { - return; - } - - if (buf.hasArray()) { - consume(buf.array(), buf.arrayOffset() + buf.position(), buf.remaining()); - buf.position(buf.limit()); - return; - } - - ensureAvailable(buf.remaining()); - 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); - System.arraycopy(tmp, 0, mArray, mSize, chunkSize); - mSize += chunkSize; - } - } - - private void ensureAvailable(int minAvailable) throws IOException { - if (minAvailable <= 0) { - return; - } - - long minCapacity = ((long) mSize) + minAvailable; - if (minCapacity <= mArray.length) { - return; - } - if (minCapacity > Integer.MAX_VALUE) { - throw new IOException( - "Required capacity too large: " + minCapacity + ", max: " + Integer.MAX_VALUE); - } - int doubleCurrentSize = (int) Math.min(mArray.length * 2L, Integer.MAX_VALUE); - int newSize = (int) Math.max(minCapacity, doubleCurrentSize); - mArray = Arrays.copyOf(mArray, newSize); - } - - @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. - return ByteBuffer.wrap(mArray, (int) offset, size).slice(); - } - - @Override - public void feed(long offset, long size, DataSink sink) throws IOException { - checkChunkValid(offset, size); - - // checkChunkValid ensures that it's OK to cast offset and size to int. - sink.consume(mArray, (int) offset, (int) size); - } - - @Override - public void copyTo(long offset, int size, ByteBuffer dest) throws IOException { - checkChunkValid(offset, size); - - // checkChunkValid ensures that it's OK to cast offset to int. - dest.put(mArray, (int) offset, size); - } - - private void checkChunkValid(long offset, long size) { - if (offset < 0) { - throw new IndexOutOfBoundsException("offset: " + offset); - } - if (size < 0) { - throw new IndexOutOfBoundsException("size: " + size); - } - if (offset > mSize) { - throw new IndexOutOfBoundsException( - "offset (" + offset + ") > source size (" + mSize + ")"); - } - long endOffset = offset + size; - if (endOffset < offset) { - throw new IndexOutOfBoundsException( - "offset (" + offset + ") + size (" + size + ") overflow"); - } - if (endOffset > mSize) { - throw new IndexOutOfBoundsException( - "offset (" + offset + ") + size (" + size + ") > source size (" + mSize + ")"); - } - } - - @Override - public DataSource slice(long offset, long size) { - checkChunkValid(offset, size); - // checkChunkValid ensures that it's OK to cast offset and size to int. - return new SliceDataSource((int) offset, (int) size); - } - - /** - * Slice of the growable byte array. The slice's offset and size in the array are fixed. - */ - private class SliceDataSource implements DataSource { - private final int mSliceOffset; - private final int mSliceSize; - - private SliceDataSource(int offset, int size) { - mSliceOffset = offset; - mSliceSize = size; - } - - @Override - public long size() { - return mSliceSize; - } - - @Override - public void feed(long offset, long size, DataSink sink) throws IOException { - checkChunkValid(offset, size); - // checkChunkValid combined with the way instances of this class are constructed ensures - // that mSliceOffset + offset does not overflow and that it's fine to cast size to int. - sink.consume(mArray, (int) (mSliceOffset + offset), (int) size); - } - - @Override - public ByteBuffer getByteBuffer(long offset, int size) throws IOException { - checkChunkValid(offset, size); - // checkChunkValid combined with the way instances of this class are constructed ensures - // that mSliceOffset + offset does not overflow. - return ByteBuffer.wrap(mArray, (int) (mSliceOffset + offset), size).slice(); - } - - @Override - public void copyTo(long offset, int size, ByteBuffer dest) throws IOException { - checkChunkValid(offset, size); - // checkChunkValid combined with the way instances of this class are constructed ensures - // that mSliceOffset + offset does not overflow. - dest.put(mArray, (int) (mSliceOffset + offset), size); - } - - @Override - public DataSource slice(long offset, long size) { - checkChunkValid(offset, size); - // checkChunkValid combined with the way instances of this class are constructed ensures - // that mSliceOffset + offset does not overflow and that it's fine to cast size to int. - return new SliceDataSource((int) (mSliceOffset + offset), (int) size); - } - - private void checkChunkValid(long offset, long size) { - if (offset < 0) { - throw new IndexOutOfBoundsException("offset: " + offset); - } - if (size < 0) { - throw new IndexOutOfBoundsException("size: " + size); - } - if (offset > mSliceSize) { - throw new IndexOutOfBoundsException( - "offset (" + offset + ") > source size (" + mSliceSize + ")"); - } - long endOffset = offset + size; - if (endOffset < offset) { - throw new IndexOutOfBoundsException( - "offset (" + offset + ") + size (" + size + ") overflow"); - } - if (endOffset > mSliceSize) { - throw new IndexOutOfBoundsException( - "offset (" + offset + ") + size (" + size + ") > source size (" + mSliceSize - + ")"); - } - } - } -} diff --git a/app/src/main/java/com/android/apksig/internal/util/ByteBufferDataSource.java b/app/src/main/java/com/android/apksig/internal/util/ByteBufferDataSource.java deleted file mode 100644 index 656c20e111..0000000000 --- a/app/src/main/java/com/android/apksig/internal/util/ByteBufferDataSource.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.util; - -import com.android.apksig.util.DataSink; -import com.android.apksig.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 IndexOutOfBoundsException("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 IndexOutOfBoundsException("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 IndexOutOfBoundsException("offset: " + offset); - } - if (size < 0) { - throw new IndexOutOfBoundsException("size: " + size); - } - if (offset > mSize) { - throw new IndexOutOfBoundsException( - "offset (" + offset + ") > source size (" + mSize + ")"); - } - long endOffset = offset + size; - if (endOffset < offset) { - throw new IndexOutOfBoundsException( - "offset (" + offset + ") + size (" + size + ") overflow"); - } - if (endOffset > mSize) { - throw new IndexOutOfBoundsException( - "offset (" + offset + ") + size (" + size + ") > source size (" + mSize +")"); - } - } -} diff --git a/app/src/main/java/com/android/apksig/internal/util/ByteBufferSink.java b/app/src/main/java/com/android/apksig/internal/util/ByteBufferSink.java deleted file mode 100644 index d7cbe03511..0000000000 --- a/app/src/main/java/com/android/apksig/internal/util/ByteBufferSink.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.util; - -import com.android.apksig.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; - } - - public ByteBuffer getBuffer() { - return mBuffer; - } - - @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/app/src/main/java/com/android/apksig/internal/util/ByteBufferUtils.java b/app/src/main/java/com/android/apksig/internal/util/ByteBufferUtils.java deleted file mode 100644 index a7b4b5c804..0000000000 --- a/app/src/main/java/com/android/apksig/internal/util/ByteBufferUtils.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.util; - -import java.nio.ByteBuffer; - -public final class ByteBufferUtils { - private ByteBufferUtils() {} - - /** - * Returns the remaining data of the provided buffer as a new byte array and advances the - * position of the buffer to the buffer's limit. - */ - public static byte[] toByteArray(ByteBuffer buf) { - byte[] result = new byte[buf.remaining()]; - buf.get(result); - return result; - } -} diff --git a/app/src/main/java/com/android/apksig/internal/util/ByteStreams.java b/app/src/main/java/com/android/apksig/internal/util/ByteStreams.java deleted file mode 100644 index bca3b0827b..0000000000 --- a/app/src/main/java/com/android/apksig/internal/util/ByteStreams.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.util; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; - -/** - * Utilities for byte arrays and I/O streams. - */ -public final class ByteStreams { - private ByteStreams() {} - - /** - * Returns the data remaining in the provided input stream as a byte array - */ - public static byte[] toByteArray(InputStream in) throws IOException { - ByteArrayOutputStream result = new ByteArrayOutputStream(); - byte[] buf = new byte[16384]; - int chunkSize; - while ((chunkSize = in.read(buf)) != -1) { - result.write(buf, 0, chunkSize); - } - return result.toByteArray(); - } -} diff --git a/app/src/main/java/com/android/apksig/internal/util/ChainedDataSource.java b/app/src/main/java/com/android/apksig/internal/util/ChainedDataSource.java deleted file mode 100644 index a0baf1aeff..0000000000 --- a/app/src/main/java/com/android/apksig/internal/util/ChainedDataSource.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.util; - -import com.android.apksig.util.DataSink; -import com.android.apksig.util.DataSource; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Arrays; - -/** Pseudo {@link DataSource} that chains the given {@link DataSource} as a continuous one. */ -public class ChainedDataSource implements DataSource { - - private final DataSource[] mSources; - private final long mTotalSize; - - public ChainedDataSource(DataSource... sources) { - mSources = sources; - mTotalSize = Arrays.stream(sources).mapToLong(src -> src.size()).sum(); - } - - @Override - public long size() { - return mTotalSize; - } - - @Override - public void feed(long offset, long size, DataSink sink) throws IOException { - if (offset + size > mTotalSize) { - throw new IndexOutOfBoundsException("Requested more than available"); - } - - for (DataSource src : mSources) { - // Offset is beyond the current source. Skip. - if (offset >= src.size()) { - offset -= src.size(); - continue; - } - - // If the remaining is enough, finish it. - long remaining = src.size() - offset; - if (remaining >= size) { - src.feed(offset, size, sink); - break; - } - - // If the remaining is not enough, consume all. - src.feed(offset, remaining, sink); - size -= remaining; - offset = 0; - } - } - - @Override - public ByteBuffer getByteBuffer(long offset, int size) throws IOException { - if (offset + size > mTotalSize) { - throw new IndexOutOfBoundsException("Requested more than available"); - } - - // Skip to the first DataSource we need. - Pair firstSource = locateDataSource(offset); - int i = firstSource.getFirst(); - offset = firstSource.getSecond(); - - // Return the current source's ByteBuffer if it fits. - if (offset + size <= mSources[i].size()) { - return mSources[i].getByteBuffer(offset, size); - } - - // Otherwise, read into a new buffer. - ByteBuffer buffer = ByteBuffer.allocate(size); - for (; i < mSources.length && buffer.hasRemaining(); i++) { - long sizeToCopy = Math.min(mSources[i].size() - offset, buffer.remaining()); - mSources[i].copyTo(offset, Math.toIntExact(sizeToCopy), buffer); - offset = 0; // may not be zero for the first source, but reset after that. - } - buffer.rewind(); - return buffer; - } - - @Override - public void copyTo(long offset, int size, ByteBuffer dest) throws IOException { - feed(offset, size, new ByteBufferSink(dest)); - } - - @Override - public DataSource slice(long offset, long size) { - // Find the first slice. - Pair firstSource = locateDataSource(offset); - int beginIndex = firstSource.getFirst(); - long beginLocalOffset = firstSource.getSecond(); - DataSource beginSource = mSources[beginIndex]; - - if (beginLocalOffset + size <= beginSource.size()) { - return beginSource.slice(beginLocalOffset, size); - } - - // Add the first slice to chaining, followed by the middle full slices, then the last. - ArrayList sources = new ArrayList<>(); - sources.add(beginSource.slice( - beginLocalOffset, beginSource.size() - beginLocalOffset)); - - Pair lastSource = locateDataSource(offset + size - 1); - int endIndex = lastSource.getFirst(); - long endLocalOffset = lastSource.getSecond(); - - for (int i = beginIndex + 1; i < endIndex; i++) { - sources.add(mSources[i]); - } - - sources.add(mSources[endIndex].slice(0, endLocalOffset + 1)); - return new ChainedDataSource(sources.toArray(new DataSource[0])); - } - - /** - * Find the index of DataSource that offset is at. - * @return Pair of DataSource index and the local offset in the DataSource. - */ - private Pair locateDataSource(long offset) { - long localOffset = offset; - for (int i = 0; i < mSources.length; i++) { - if (localOffset < mSources[i].size()) { - return Pair.of(i, localOffset); - } - localOffset -= mSources[i].size(); - } - throw new IndexOutOfBoundsException("Access is out of bound, offset: " + offset + - ", totalSize: " + mTotalSize); - } -} diff --git a/app/src/main/java/com/android/apksig/internal/util/DelegatingX509Certificate.java b/app/src/main/java/com/android/apksig/internal/util/DelegatingX509Certificate.java deleted file mode 100644 index 8f9e1fd22f..0000000000 --- a/app/src/main/java/com/android/apksig/internal/util/DelegatingX509Certificate.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.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.Provider; -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.CertificateParsingException; -import java.security.cert.X509Certificate; -import java.util.Collection; -import java.util.Date; -import java.util.List; -import java.util.Set; -import javax.security.auth.x500.X500Principal; - -/** - * {@link X509Certificate} which delegates all method invocations to the provided delegate - * {@code X509Certificate}. - */ -public class DelegatingX509Certificate extends X509Certificate { - private static final long serialVersionUID = 1L; - - private final X509Certificate mDelegate; - - public DelegatingX509Certificate(X509Certificate delegate) { - this.mDelegate = delegate; - } - - @Override - public Set getCriticalExtensionOIDs() { - return mDelegate.getCriticalExtensionOIDs(); - } - - @Override - public byte[] getExtensionValue(String oid) { - return mDelegate.getExtensionValue(oid); - } - - @Override - public Set 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(); - } - - @Override - public X500Principal getIssuerX500Principal() { - return mDelegate.getIssuerX500Principal(); - } - - @Override - public X500Principal getSubjectX500Principal() { - return mDelegate.getSubjectX500Principal(); - } - - @Override - public List getExtendedKeyUsage() throws CertificateParsingException { - return mDelegate.getExtendedKeyUsage(); - } - - @Override - public Collection> getSubjectAlternativeNames() throws CertificateParsingException { - return mDelegate.getSubjectAlternativeNames(); - } - - @Override - public Collection> getIssuerAlternativeNames() throws CertificateParsingException { - return mDelegate.getIssuerAlternativeNames(); - } - - @Override - public void verify(PublicKey key, Provider sigProvider) throws CertificateException, - NoSuchAlgorithmException, InvalidKeyException, SignatureException { - mDelegate.verify(key, sigProvider); - } -} diff --git a/app/src/main/java/com/android/apksig/internal/util/FileChannelDataSource.java b/app/src/main/java/com/android/apksig/internal/util/FileChannelDataSource.java deleted file mode 100644 index e4a421a72c..0000000000 --- a/app/src/main/java/com/android/apksig/internal/util/FileChannelDataSource.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.util; - -import com.android.apksig.util.DataSink; -import com.android.apksig.util.DataSource; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.BufferOverflowException; -import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; - -/** - * {@link DataSource} backed by a {@link FileChannel} for {@link RandomAccessFile} access. - */ -public class FileChannelDataSource implements DataSource { - - private static final int MAX_READ_CHUNK_SIZE = 1024 * 1024; - - private final FileChannel mChannel; - private final long mOffset; - private final long mSize; - - /** - * Constructs a new {@code FileChannelDataSource} based on the data contained in the - * whole file. Changes to the contents of the file, including the size of the file, - * will be visible in this data source. - */ - public FileChannelDataSource(FileChannel channel) { - mChannel = channel; - mOffset = 0; - mSize = -1; - } - - /** - * Constructs a new {@code FileChannelDataSource} 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. - * - * @throws IndexOutOfBoundsException if {@code offset} or {@code size} is negative. - */ - public FileChannelDataSource(FileChannel channel, long offset, long size) { - if (offset < 0) { - throw new IndexOutOfBoundsException("offset: " + size); - } - if (size < 0) { - throw new IndexOutOfBoundsException("size: " + size); - } - mChannel = channel; - mOffset = offset; - mSize = size; - } - - @Override - public long size() { - if (mSize == -1) { - try { - return mChannel.size(); - } catch (IOException e) { - return 0; - } - } else { - return mSize; - } - } - - @Override - public FileChannelDataSource slice(long offset, long size) { - long sourceSize = size(); - checkChunkValid(offset, size, sourceSize); - if ((offset == 0) && (size == sourceSize)) { - return this; - } - - return new FileChannelDataSource(mChannel, 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; - ByteBuffer buf = ByteBuffer.allocateDirect((int) Math.min(remaining, MAX_READ_CHUNK_SIZE)); - - while (remaining > 0) { - int chunkSize = (int) Math.min(remaining, buf.capacity()); - int chunkRemaining = chunkSize; - buf.limit(chunkSize); - synchronized (mChannel) { - mChannel.position(chunkOffsetInFile); - while (chunkRemaining > 0) { - int read = mChannel.read(buf); - if (read < 0) { - throw new IOException("Unexpected EOF encountered"); - } - chunkRemaining -= read; - } - } - buf.flip(); - sink.consume(buf); - buf.clear(); - 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; - } - if (size > dest.remaining()) { - throw new BufferOverflowException(); - } - - long offsetInFile = mOffset + offset; - int remaining = size; - int prevLimit = dest.limit(); - try { - // FileChannel.read(ByteBuffer) reads up to dest.remaining(). Thus, we need to adjust - // the buffer's limit to avoid reading more than size bytes. - dest.limit(dest.position() + size); - while (remaining > 0) { - int chunkSize; - synchronized (mChannel) { - mChannel.position(offsetInFile); - chunkSize = mChannel.read(dest); - } - offsetInFile += chunkSize; - remaining -= chunkSize; - } - } finally { - dest.limit(prevLimit); - } - } - - @Override - public ByteBuffer getByteBuffer(long offset, int size) throws IOException { - if (size < 0) { - throw new IndexOutOfBoundsException("size: " + size); - } - 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 IndexOutOfBoundsException("offset: " + offset); - } - if (size < 0) { - throw new IndexOutOfBoundsException("size: " + size); - } - if (offset > sourceSize) { - throw new IndexOutOfBoundsException( - "offset (" + offset + ") > source size (" + sourceSize + ")"); - } - long endOffset = offset + size; - if (endOffset < offset) { - throw new IndexOutOfBoundsException( - "offset (" + offset + ") + size (" + size + ") overflow"); - } - if (endOffset > sourceSize) { - throw new IndexOutOfBoundsException( - "offset (" + offset + ") + size (" + size - + ") > source size (" + sourceSize +")"); - } - } -} diff --git a/app/src/main/java/com/android/apksig/internal/util/GuaranteedEncodedFormX509Certificate.java b/app/src/main/java/com/android/apksig/internal/util/GuaranteedEncodedFormX509Certificate.java deleted file mode 100644 index 958cd12aa3..0000000000 --- a/app/src/main/java/com/android/apksig/internal/util/GuaranteedEncodedFormX509Certificate.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.util; - -import java.security.cert.CertificateEncodingException; -import java.security.cert.X509Certificate; -import java.util.Arrays; - -/** - * {@link X509Certificate} whose {@link #getEncoded()} returns the data provided at construction - * time. - */ -public class GuaranteedEncodedFormX509Certificate extends DelegatingX509Certificate { - private static final long serialVersionUID = 1L; - - private final byte[] mEncodedForm; - private int mHash = -1; - - 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; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof X509Certificate)) return false; - - try { - byte[] a = this.getEncoded(); - byte[] b = ((X509Certificate) o).getEncoded(); - return Arrays.equals(a, b); - } catch (CertificateEncodingException e) { - return false; - } - } - - @Override - public int hashCode() { - if (mHash == -1) { - try { - mHash = Arrays.hashCode(this.getEncoded()); - } catch (CertificateEncodingException e) { - mHash = 0; - } - } - return mHash; - } -} diff --git a/app/src/main/java/com/android/apksig/internal/util/InclusiveIntRange.java b/app/src/main/java/com/android/apksig/internal/util/InclusiveIntRange.java deleted file mode 100644 index d7866a9edd..0000000000 --- a/app/src/main/java/com/android/apksig/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.apksig.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 getValuesNotIn( - List sortedNonOverlappingRanges) { - if (sortedNonOverlappingRanges.isEmpty()) { - return Collections.singletonList(this); - } - - int testValue = min; - List 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/app/src/main/java/com/android/apksig/internal/util/MessageDigestSink.java b/app/src/main/java/com/android/apksig/internal/util/MessageDigestSink.java deleted file mode 100644 index 733dd563ce..0000000000 --- a/app/src/main/java/com/android/apksig/internal/util/MessageDigestSink.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.apksig.internal.util; - -import com.android.apksig.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/app/src/main/java/com/android/apksig/internal/util/OutputStreamDataSink.java b/app/src/main/java/com/android/apksig/internal/util/OutputStreamDataSink.java deleted file mode 100644 index f1b5ac6c5e..0000000000 --- a/app/src/main/java/com/android/apksig/internal/util/OutputStreamDataSink.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.util; - -import com.android.apksig.util.DataSink; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; - -/** - * {@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/app/src/main/java/com/android/apksig/internal/util/Pair.java b/app/src/main/java/com/android/apksig/internal/util/Pair.java deleted file mode 100644 index 7f9ee520f1..0000000000 --- a/app/src/main/java/com/android/apksig/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.apksig.internal.util; - -/** - * Pair of two elements. - */ -public final class Pair { - private final A mFirst; - private final B mSecond; - - private Pair(A first, B second) { - mFirst = first; - mSecond = second; - } - - public static Pair of(A first, B second) { - return new Pair(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/app/src/main/java/com/android/apksig/internal/util/RandomAccessFileDataSink.java b/app/src/main/java/com/android/apksig/internal/util/RandomAccessFileDataSink.java deleted file mode 100644 index bbd2d14a8a..0000000000 --- a/app/src/main/java/com/android/apksig/internal/util/RandomAccessFileDataSink.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.util; - -import com.android.apksig.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; - } - - /** - * Returns the underlying {@link RandomAccessFile}. - */ - public RandomAccessFile getFile() { - return mFile; - } - - @Override - public void consume(byte[] buf, int offset, int length) throws IOException { - if (offset < 0) { - // Must perform this check here because RandomAccessFile.write doesn't throw when offset - // is negative but length is 0 - throw new IndexOutOfBoundsException("offset: " + offset); - } - if (offset > buf.length) { - // Must perform this check here because RandomAccessFile.write doesn't throw when offset - // is too large but length is 0 - throw new IndexOutOfBoundsException( - "offset: " + offset + ", buf.length: " + buf.length); - } - 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/app/src/main/java/com/android/apksig/internal/util/TeeDataSink.java b/app/src/main/java/com/android/apksig/internal/util/TeeDataSink.java deleted file mode 100644 index 2e46f18b05..0000000000 --- a/app/src/main/java/com/android/apksig/internal/util/TeeDataSink.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.util; - -import com.android.apksig.util.DataSink; -import java.io.IOException; -import java.nio.ByteBuffer; - -/** - * {@link DataSink} which copies provided input into each of the sinks provided to it. - */ -public class TeeDataSink implements DataSink { - - private final DataSink[] mSinks; - - public TeeDataSink(DataSink[] sinks) { - mSinks = sinks; - } - - @Override - public void consume(byte[] buf, int offset, int length) throws IOException { - for (DataSink sink : mSinks) { - sink.consume(buf, offset, length); - } - } - - @Override - public void consume(ByteBuffer buf) throws IOException { - int originalPosition = buf.position(); - for (int i = 0; i < mSinks.length; i++) { - if (i > 0) { - buf.position(originalPosition); - } - mSinks[i].consume(buf); - } - } -} diff --git a/app/src/main/java/com/android/apksig/internal/util/VerityTreeBuilder.java b/app/src/main/java/com/android/apksig/internal/util/VerityTreeBuilder.java deleted file mode 100644 index 81026ba5ff..0000000000 --- a/app/src/main/java/com/android/apksig/internal/util/VerityTreeBuilder.java +++ /dev/null @@ -1,325 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.util; - -import static java.util.concurrent.TimeUnit.MILLISECONDS; - -import com.android.apksig.internal.zip.ZipUtils; -import com.android.apksig.util.DataSink; -import com.android.apksig.util.DataSource; -import com.android.apksig.util.DataSources; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -import java.util.ArrayList; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Phaser; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -/** - * VerityTreeBuilder is used to generate the root hash of verity tree built from the input file. - * The root hash can be used on device for on-access verification. The tree itself is reproducible - * on device, and is not shipped with the APK. - */ -public class VerityTreeBuilder implements AutoCloseable { - - /** - * Maximum size (in bytes) of each node of the tree. - */ - private final static int CHUNK_SIZE = 4096; - /** - * Maximum parallelism while calculating digests. - */ - private final static int DIGEST_PARALLELISM = Math.min(32, - Runtime.getRuntime().availableProcessors()); - /** - * Queue size. - */ - private final static int MAX_OUTSTANDING_CHUNKS = 4; - /** - * Typical prefetch size. - */ - private final static int MAX_PREFETCH_CHUNKS = 1024; - /** - * Minimum chunks to be processed by a single worker task. - */ - private final static int MIN_CHUNKS_PER_WORKER = 8; - - /** - * Digest algorithm (JCA Digest algorithm name) used in the tree. - */ - private final static String JCA_ALGORITHM = "SHA-256"; - - /** - * Optional salt to apply before each digestion. - */ - private final byte[] mSalt; - - private final MessageDigest mMd; - - private final ExecutorService mExecutor = - new ThreadPoolExecutor(DIGEST_PARALLELISM, DIGEST_PARALLELISM, - 0L, MILLISECONDS, - new ArrayBlockingQueue<>(MAX_OUTSTANDING_CHUNKS), - new ThreadPoolExecutor.CallerRunsPolicy()); - - public VerityTreeBuilder(byte[] salt) throws NoSuchAlgorithmException { - mSalt = salt; - mMd = getNewMessageDigest(); - } - - @Override - public void close() { - mExecutor.shutdownNow(); - } - - /** - * Returns the root hash of the APK verity tree built from ZIP blocks. - * - * Specifically, APK verity tree is built from the APK, but as if the APK Signing Block (which - * must be page aligned) and the "Central Directory offset" field in End of Central Directory - * are skipped. - */ - public byte[] generateVerityTreeRootHash(DataSource beforeApkSigningBlock, - DataSource centralDir, DataSource eocd) throws IOException { - if (beforeApkSigningBlock.size() % CHUNK_SIZE != 0) { - throw new IllegalStateException("APK Signing Block size not a multiple of " + CHUNK_SIZE - + ": " + beforeApkSigningBlock.size()); - } - - // 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 = beforeApkSigningBlock.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); - - return generateVerityTreeRootHash(new ChainedDataSource(beforeApkSigningBlock, centralDir, - DataSources.asDataSource(eocdBuf))); - } - - /** - * Returns the root hash of the verity tree built from the data source. - */ - public byte[] generateVerityTreeRootHash(DataSource fileSource) throws IOException { - ByteBuffer verityBuffer = generateVerityTree(fileSource); - return getRootHashFromTree(verityBuffer); - } - - /** - * Returns the byte buffer that contains the whole verity tree. - * - * The tree is built bottom up. The bottom level has 256-bit digest for each 4 KB block in the - * input file. If the total size is larger than 4 KB, take this level as input and repeat the - * same procedure, until the level is within 4 KB. If salt is given, it will apply to each - * digestion before the actual data. - * - * The returned root hash is calculated from the last level of 4 KB chunk, similarly with salt. - * - * The tree is currently stored only in memory and is never written out. Nevertheless, it is - * the actual verity tree format on disk, and is supposed to be re-generated on device. - */ - public ByteBuffer generateVerityTree(DataSource fileSource) throws IOException { - int digestSize = mMd.getDigestLength(); - - // Calculate the summed area table of level size. In other word, this is the offset - // table of each level, plus the next non-existing level. - int[] levelOffset = calculateLevelOffset(fileSource.size(), digestSize); - - ByteBuffer verityBuffer = ByteBuffer.allocate(levelOffset[levelOffset.length - 1]); - - // Generate the hash tree bottom-up. - for (int i = levelOffset.length - 2; i >= 0; i--) { - DataSink middleBufferSink = new ByteBufferSink( - slice(verityBuffer, levelOffset[i], levelOffset[i + 1])); - DataSource src; - if (i == levelOffset.length - 2) { - src = fileSource; - digestDataByChunks(src, middleBufferSink); - } else { - src = DataSources.asDataSource(slice(verityBuffer.asReadOnlyBuffer(), - levelOffset[i + 1], levelOffset[i + 2])); - digestDataByChunks(src, middleBufferSink); - } - - // If the output is not full chunk, pad with 0s. - long totalOutput = divideRoundup(src.size(), CHUNK_SIZE) * digestSize; - int incomplete = (int) (totalOutput % CHUNK_SIZE); - if (incomplete > 0) { - byte[] padding = new byte[CHUNK_SIZE - incomplete]; - middleBufferSink.consume(padding, 0, padding.length); - } - } - return verityBuffer; - } - - /** - * Returns the digested root hash from the top level (only page) of a verity tree. - */ - public byte[] getRootHashFromTree(ByteBuffer verityBuffer) throws IOException { - ByteBuffer firstPage = slice(verityBuffer.asReadOnlyBuffer(), 0, CHUNK_SIZE); - return saltedDigest(firstPage); - } - - /** - * Returns an array of summed area table of level size in the verity tree. In other words, the - * returned array is offset of each level in the verity tree file format, plus an additional - * offset of the next non-existing level (i.e. end of the last level + 1). Thus the array size - * is level + 1. - */ - private static int[] calculateLevelOffset(long dataSize, int digestSize) { - // Compute total size of each level, bottom to top. - ArrayList levelSize = new ArrayList<>(); - while (true) { - long chunkCount = divideRoundup(dataSize, CHUNK_SIZE); - long size = CHUNK_SIZE * divideRoundup(chunkCount * digestSize, CHUNK_SIZE); - levelSize.add(size); - if (chunkCount * digestSize <= CHUNK_SIZE) { - break; - } - dataSize = chunkCount * digestSize; - } - - // Reverse and convert to summed area table. - int[] levelOffset = new int[levelSize.size() + 1]; - levelOffset[0] = 0; - for (int i = 0; i < levelSize.size(); i++) { - // We don't support verity tree if it is larger then Integer.MAX_VALUE. - levelOffset[i + 1] = levelOffset[i] + Math.toIntExact( - levelSize.get(levelSize.size() - i - 1)); - } - return levelOffset; - } - - /** - * Digest data source by chunks then feeds them to the sink one by one. If the last unit is - * less than the chunk size and padding is desired, feed with extra padding 0 to fill up the - * chunk before digesting. - */ - private void digestDataByChunks(DataSource dataSource, DataSink dataSink) throws IOException { - final long size = dataSource.size(); - final int chunks = (int) divideRoundup(size, CHUNK_SIZE); - - /** Single IO operation size, in chunks. */ - final int ioSizeChunks = MAX_PREFETCH_CHUNKS; - - final byte[][] hashes = new byte[chunks][]; - - Phaser tasks = new Phaser(1); - - // Reading the input file as fast as we can. - final long maxReadSize = ioSizeChunks * CHUNK_SIZE; - - long readOffset = 0; - int startChunkIndex = 0; - while (readOffset < size) { - final long readLimit = Math.min(readOffset + maxReadSize, size); - final int readSize = (int) (readLimit - readOffset); - final int bufferSizeChunks = (int) divideRoundup(readSize, CHUNK_SIZE); - - // Overllocating to zero-pad last chunk. - // With 4MiB block size, 32 threads and 4 queue size we might allocate up to 144MiB. - final ByteBuffer buffer = ByteBuffer.allocate(bufferSizeChunks * CHUNK_SIZE); - dataSource.copyTo(readOffset, readSize, buffer); - buffer.rewind(); - - final int readChunkIndex = startChunkIndex; - Runnable task = () -> { - final MessageDigest md = cloneMessageDigest(); - for (int offset = 0, finish = buffer.capacity(), chunkIndex = readChunkIndex; - offset < finish; offset += CHUNK_SIZE, ++chunkIndex) { - ByteBuffer chunk = slice(buffer, offset, offset + CHUNK_SIZE); - hashes[chunkIndex] = saltedDigest(md, chunk); - } - tasks.arriveAndDeregister(); - }; - tasks.register(); - mExecutor.execute(task); - - startChunkIndex += bufferSizeChunks; - readOffset += readSize; - } - - // Waiting for the tasks to complete. - tasks.arriveAndAwaitAdvance(); - - // Streaming hashes back. - for (byte[] hash : hashes) { - dataSink.consume(hash, 0, hash.length); - } - } - - /** Returns the digest of data with salt prepended. */ - private byte[] saltedDigest(ByteBuffer data) { - return saltedDigest(mMd, data); - } - - private byte[] saltedDigest(MessageDigest md, ByteBuffer data) { - md.reset(); - if (mSalt != null) { - md.update(mSalt); - } - md.update(data); - return md.digest(); - } - - /** Divides a number and round up to the closest integer. */ - private static long divideRoundup(long dividend, long divisor) { - return (dividend + divisor - 1) / divisor; - } - - /** Returns a slice of the buffer with shared the content. */ - private static ByteBuffer slice(ByteBuffer buffer, int begin, int end) { - ByteBuffer b = buffer.duplicate(); - b.position(0); // to ensure position <= limit invariant. - b.limit(end); - b.position(begin); - return b.slice(); - } - - /** - * Obtains a new instance of the message digest algorithm. - */ - private static MessageDigest getNewMessageDigest() throws NoSuchAlgorithmException { - return MessageDigest.getInstance(JCA_ALGORITHM); - } - - /** - * Clones the existing message digest, or creates a new instance if clone is unavailable. - */ - private MessageDigest cloneMessageDigest() { - try { - return (MessageDigest) mMd.clone(); - } catch (CloneNotSupportedException ignored) { - try { - return getNewMessageDigest(); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException( - "Failed to obtain an instance of a previously available message digest", e); - } - } - } -} diff --git a/app/src/main/java/com/android/apksig/internal/util/X509CertificateUtils.java b/app/src/main/java/com/android/apksig/internal/util/X509CertificateUtils.java deleted file mode 100644 index 9a266f2673..0000000000 --- a/app/src/main/java/com/android/apksig/internal/util/X509CertificateUtils.java +++ /dev/null @@ -1,278 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.util; - -import com.android.apksig.internal.asn1.Asn1BerParser; -import com.android.apksig.internal.asn1.Asn1DecodingException; -import com.android.apksig.internal.asn1.Asn1DerEncoder; -import com.android.apksig.internal.asn1.Asn1EncodingException; -import com.android.apksig.internal.x509.Certificate; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Base64; -import java.util.Collection; - -/** - * Provides methods to generate {@code X509Certificate}s from their encoded form. These methods - * can be used to generate certificates that would be rejected by the Java {@code - * CertificateFactory}. - */ -public class X509CertificateUtils { - - private static CertificateFactory sCertFactory = null; - - // The PEM certificate header and footer as specified in RFC 7468: - // There is exactly one space character (SP) separating the "BEGIN" or - // "END" from the label. There are exactly five hyphen-minus (also - // known as dash) characters ("-") on both ends of the encapsulation - // boundaries, no more, no less. - public static final byte[] BEGIN_CERT_HEADER = "-----BEGIN CERTIFICATE-----".getBytes(); - public static final byte[] END_CERT_FOOTER = "-----END CERTIFICATE-----".getBytes(); - - private static void buildCertFactory() { - if (sCertFactory != null) { - return; - } - try { - sCertFactory = CertificateFactory.getInstance("X.509"); - } catch (CertificateException e) { - throw new RuntimeException("Failed to create X.509 CertificateFactory", e); - } - } - - /** - * Generates an {@code X509Certificate} from the {@code InputStream}. - * - * @throws CertificateException if the {@code InputStream} cannot be decoded to a valid - * certificate. - */ - public static X509Certificate generateCertificate(InputStream in) throws CertificateException { - byte[] encodedForm; - try { - encodedForm = ByteStreams.toByteArray(in); - } catch (IOException e) { - throw new CertificateException("Failed to parse certificate", e); - } - return generateCertificate(encodedForm); - } - - /** - * Generates an {@code X509Certificate} from the encoded form. - * - * @throws CertificateException if the encodedForm cannot be decoded to a valid certificate. - */ - public static X509Certificate generateCertificate(byte[] encodedForm) - throws CertificateException { - if (sCertFactory == null) { - buildCertFactory(); - } - return generateCertificate(encodedForm, sCertFactory); - } - - /** - * Generates an {@code X509Certificate} from the encoded form using the provided - * {@code CertificateFactory}. - * - * @throws CertificateException if the encodedForm cannot be decoded to a valid certificate. - */ - public static X509Certificate generateCertificate(byte[] encodedForm, - CertificateFactory certFactory) throws CertificateException { - X509Certificate certificate; - try { - certificate = (X509Certificate) certFactory.generateCertificate( - new ByteArrayInputStream(encodedForm)); - return certificate; - } catch (CertificateException e) { - // This could be expected if the certificate is encoded using a BER encoding that does - // not use the minimum number of bytes to represent the length of the contents; attempt - // to decode the certificate using the BER parser and re-encode using the DER encoder - // below. - } - try { - // Some apps were previously signed with a BER encoded certificate that now results - // in exceptions from the CertificateFactory generateCertificate(s) methods. Since - // the original BER encoding of the certificate is used as the signature for these - // apps that original encoding must be maintained when signing updated versions of - // these apps and any new apps that may require capabilities guarded by the - // signature. To maintain the same signature the BER parser can be used to parse - // the certificate, then it can be re-encoded to its DER equivalent which is - // accepted by the generateCertificate method. The positions in the ByteBuffer can - // then be used with the GuaranteedEncodedFormX509Certificate object to ensure the - // getEncoded method returns the original signature of the app. - ByteBuffer encodedCertBuffer = getNextDEREncodedCertificateBlock( - ByteBuffer.wrap(encodedForm)); - int startingPos = encodedCertBuffer.position(); - Certificate reencodedCert = Asn1BerParser.parse(encodedCertBuffer, Certificate.class); - byte[] reencodedForm = Asn1DerEncoder.encode(reencodedCert); - certificate = (X509Certificate) certFactory.generateCertificate( - new ByteArrayInputStream(reencodedForm)); - // If the reencodedForm is successfully accepted by the CertificateFactory then copy the - // original encoding from the ByteBuffer and use that encoding in the Guaranteed object. - byte[] originalEncoding = new byte[encodedCertBuffer.position() - startingPos]; - encodedCertBuffer.position(startingPos); - encodedCertBuffer.get(originalEncoding); - GuaranteedEncodedFormX509Certificate guaranteedEncodedCert = - new GuaranteedEncodedFormX509Certificate(certificate, originalEncoding); - return guaranteedEncodedCert; - } catch (Asn1DecodingException | Asn1EncodingException | CertificateException e) { - throw new CertificateException("Failed to parse certificate", e); - } - } - - /** - * Generates a {@code Collection} of {@code Certificate} objects from the encoded {@code - * InputStream}. - * - * @throws CertificateException if the InputStream cannot be decoded to zero or more valid - * {@code Certificate} objects. - */ - public static Collection generateCertificates( - InputStream in) throws CertificateException { - if (sCertFactory == null) { - buildCertFactory(); - } - return generateCertificates(in, sCertFactory); - } - - /** - * Generates a {@code Collection} of {@code Certificate} objects from the encoded {@code - * InputStream} using the provided {@code CertificateFactory}. - * - * @throws CertificateException if the InputStream cannot be decoded to zero or more valid - * {@code Certificates} objects. - */ - public static Collection generateCertificates( - InputStream in, CertificateFactory certFactory) throws CertificateException { - // Since the InputStream is not guaranteed to support mark / reset operations first read it - // into a byte array to allow using the BER parser / DER encoder if it cannot be read by - // the CertificateFactory. - byte[] encodedCerts; - try { - encodedCerts = ByteStreams.toByteArray(in); - } catch (IOException e) { - throw new CertificateException("Failed to read the input stream", e); - } - try { - return certFactory.generateCertificates(new ByteArrayInputStream(encodedCerts)); - } catch (CertificateException e) { - // This could be expected if the certificates are encoded using a BER encoding that does - // not use the minimum number of bytes to represent the length of the contents; attempt - // to decode the certificates using the BER parser and re-encode using the DER encoder - // below. - } - try { - Collection certificates = new ArrayList<>(1); - ByteBuffer encodedCertsBuffer = ByteBuffer.wrap(encodedCerts); - while (encodedCertsBuffer.hasRemaining()) { - ByteBuffer certBuffer = getNextDEREncodedCertificateBlock(encodedCertsBuffer); - int startingPos = certBuffer.position(); - Certificate reencodedCert = Asn1BerParser.parse(certBuffer, Certificate.class); - byte[] reencodedForm = Asn1DerEncoder.encode(reencodedCert); - X509Certificate certificate = (X509Certificate) certFactory.generateCertificate( - new ByteArrayInputStream(reencodedForm)); - byte[] originalEncoding = new byte[certBuffer.position() - startingPos]; - certBuffer.position(startingPos); - certBuffer.get(originalEncoding); - GuaranteedEncodedFormX509Certificate guaranteedEncodedCert = - new GuaranteedEncodedFormX509Certificate(certificate, originalEncoding); - certificates.add(guaranteedEncodedCert); - } - return certificates; - } catch (Asn1DecodingException | Asn1EncodingException e) { - throw new CertificateException("Failed to parse certificates", e); - } - } - - /** - * Parses the provided ByteBuffer to obtain the next certificate in DER encoding. If the buffer - * does not begin with the PEM certificate header then it is returned with the assumption that - * it is already DER encoded. If the buffer does begin with the PEM certificate header then the - * certificate data is read from the buffer until the PEM certificate footer is reached; this - * data is then base64 decoded and returned in a new ByteBuffer. - * - * If the buffer is in PEM format then the position of the buffer is moved to the end of the - * current certificate; if the buffer is already DER encoded then the position of the buffer is - * not modified. - * - * @throws CertificateException if the buffer contains the PEM certificate header but does not - * contain the expected footer. - */ - private static ByteBuffer getNextDEREncodedCertificateBlock(ByteBuffer certificateBuffer) - throws CertificateException { - if (certificateBuffer == null) { - throw new NullPointerException("The certificateBuffer cannot be null"); - } - // if the buffer does not contain enough data for the PEM cert header then just return the - // provided buffer. - if (certificateBuffer.remaining() < BEGIN_CERT_HEADER.length) { - return certificateBuffer; - } - certificateBuffer.mark(); - for (int i = 0; i < BEGIN_CERT_HEADER.length; i++) { - if (certificateBuffer.get() != BEGIN_CERT_HEADER[i]) { - certificateBuffer.reset(); - return certificateBuffer; - } - } - StringBuilder pemEncoding = new StringBuilder(); - while (certificateBuffer.hasRemaining()) { - char encodedChar = (char) certificateBuffer.get(); - // if the current character is a '-' then the beginning of the footer has been reached - if (encodedChar == '-') { - break; - } else if (Character.isWhitespace(encodedChar)) { - continue; - } else { - pemEncoding.append(encodedChar); - } - } - // start from the second index in the certificate footer since the first '-' should have - // been consumed above. - for (int i = 1; i < END_CERT_FOOTER.length; i++) { - if (!certificateBuffer.hasRemaining()) { - throw new CertificateException( - "The provided input contains the PEM certificate header but does not " - + "contain sufficient data for the footer"); - } - if (certificateBuffer.get() != END_CERT_FOOTER[i]) { - throw new CertificateException( - "The provided input contains the PEM certificate header without a " - + "valid certificate footer"); - } - } - byte[] derEncoding = Base64.getDecoder().decode(pemEncoding.toString()); - // consume any trailing whitespace in the byte buffer - int nextEncodedChar = certificateBuffer.position(); - while (certificateBuffer.hasRemaining()) { - char trailingChar = (char) certificateBuffer.get(); - if (Character.isWhitespace(trailingChar)) { - nextEncodedChar++; - } else { - break; - } - } - certificateBuffer.position(nextEncodedChar); - return ByteBuffer.wrap(derEncoding); - } -} diff --git a/app/src/main/java/com/android/apksig/internal/x509/AttributeTypeAndValue.java b/app/src/main/java/com/android/apksig/internal/x509/AttributeTypeAndValue.java deleted file mode 100644 index 077db232ba..0000000000 --- a/app/src/main/java/com/android/apksig/internal/x509/AttributeTypeAndValue.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.x509; - -import com.android.apksig.internal.asn1.Asn1Class; -import com.android.apksig.internal.asn1.Asn1Field; -import com.android.apksig.internal.asn1.Asn1OpaqueObject; -import com.android.apksig.internal.asn1.Asn1Type; - -/** - * {@code AttributeTypeAndValue} as specified in RFC 5280. - */ -@Asn1Class(type = Asn1Type.SEQUENCE) -public class AttributeTypeAndValue { - - @Asn1Field(index = 0, type = Asn1Type.OBJECT_IDENTIFIER) - public String attrType; - - @Asn1Field(index = 1, type = Asn1Type.ANY) - public Asn1OpaqueObject attrValue; -} \ No newline at end of file diff --git a/app/src/main/java/com/android/apksig/internal/x509/Certificate.java b/app/src/main/java/com/android/apksig/internal/x509/Certificate.java deleted file mode 100644 index 70ff6a163c..0000000000 --- a/app/src/main/java/com/android/apksig/internal/x509/Certificate.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.x509; - -import com.android.apksig.internal.asn1.Asn1Class; -import com.android.apksig.internal.asn1.Asn1Field; -import com.android.apksig.internal.asn1.Asn1OpaqueObject; -import com.android.apksig.internal.asn1.Asn1Type; -import com.android.apksig.internal.pkcs7.AlgorithmIdentifier; -import com.android.apksig.internal.pkcs7.IssuerAndSerialNumber; -import com.android.apksig.internal.pkcs7.SignerIdentifier; -import com.android.apksig.internal.util.ByteBufferUtils; -import com.android.apksig.internal.util.GuaranteedEncodedFormX509Certificate; -import com.android.apksig.internal.util.X509CertificateUtils; - -import java.math.BigInteger; -import java.nio.ByteBuffer; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -import javax.security.auth.x500.X500Principal; - -/** - * X509 {@code Certificate} as specified in RFC 5280. - */ -@Asn1Class(type = Asn1Type.SEQUENCE) -public class Certificate { - @Asn1Field(index = 0, type = Asn1Type.SEQUENCE) - public TBSCertificate certificate; - - @Asn1Field(index = 1, type = Asn1Type.SEQUENCE) - public AlgorithmIdentifier signatureAlgorithm; - - @Asn1Field(index = 2, type = Asn1Type.BIT_STRING) - public ByteBuffer signature; - - public static X509Certificate findCertificate( - Collection certs, SignerIdentifier id) { - for (X509Certificate cert : certs) { - if (isMatchingCerticicate(cert, id)) { - return cert; - } - } - return null; - } - - private static boolean isMatchingCerticicate(X509Certificate cert, SignerIdentifier id) { - if (id.issuerAndSerialNumber == null) { - // Android doesn't support any other means of identifying the signing certificate - return false; - } - IssuerAndSerialNumber issuerAndSerialNumber = id.issuerAndSerialNumber; - byte[] encodedIssuer = - ByteBufferUtils.toByteArray(issuerAndSerialNumber.issuer.getEncoded()); - X500Principal idIssuer = new X500Principal(encodedIssuer); - BigInteger idSerialNumber = issuerAndSerialNumber.certificateSerialNumber; - return idSerialNumber.equals(cert.getSerialNumber()) - && idIssuer.equals(cert.getIssuerX500Principal()); - } - - public static List parseCertificates( - List encodedCertificates) throws CertificateException { - if (encodedCertificates.isEmpty()) { - return Collections.emptyList(); - } - - List result = new ArrayList<>(encodedCertificates.size()); - for (int i = 0; i < encodedCertificates.size(); i++) { - Asn1OpaqueObject encodedCertificate = encodedCertificates.get(i); - X509Certificate certificate; - byte[] encodedForm = ByteBufferUtils.toByteArray(encodedCertificate.getEncoded()); - try { - certificate = X509CertificateUtils.generateCertificate(encodedForm); - } catch (CertificateException e) { - throw new CertificateException("Failed to parse certificate #" + (i + 1), e); - } - // Wrap the cert so that the result's getEncoded returns exactly the original - // encoded form. Without this, getEncoded may return a different form from what was - // stored in the signature. This is because some X509Certificate(Factory) - // implementations re-encode certificates and/or some implementations of - // X509Certificate.getEncoded() re-encode certificates. - certificate = new GuaranteedEncodedFormX509Certificate(certificate, encodedForm); - result.add(certificate); - } - return result; - } -} diff --git a/app/src/main/java/com/android/apksig/internal/x509/Extension.java b/app/src/main/java/com/android/apksig/internal/x509/Extension.java deleted file mode 100644 index bf37c1e824..0000000000 --- a/app/src/main/java/com/android/apksig/internal/x509/Extension.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.x509; - -import com.android.apksig.internal.asn1.Asn1Class; -import com.android.apksig.internal.asn1.Asn1Field; -import com.android.apksig.internal.asn1.Asn1Type; - -import java.nio.ByteBuffer; - -/** - * X509 {@code Extension} as specified in RFC 5280. - */ -@Asn1Class(type = Asn1Type.SEQUENCE) -public class Extension { - @Asn1Field(index = 0, type = Asn1Type.OBJECT_IDENTIFIER) - public String extensionID; - - @Asn1Field(index = 1, type = Asn1Type.BOOLEAN, optional = true) - public boolean isCritial = false; - - @Asn1Field(index = 2, type = Asn1Type.OCTET_STRING) - public ByteBuffer extensionValue; -} diff --git a/app/src/main/java/com/android/apksig/internal/x509/Name.java b/app/src/main/java/com/android/apksig/internal/x509/Name.java deleted file mode 100644 index 08400d6814..0000000000 --- a/app/src/main/java/com/android/apksig/internal/x509/Name.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.x509; - -import com.android.apksig.internal.asn1.Asn1Class; -import com.android.apksig.internal.asn1.Asn1Field; -import com.android.apksig.internal.asn1.Asn1Type; - -import java.util.List; - -/** - * X501 {@code Name} as specified in RFC 5280. - */ -@Asn1Class(type = Asn1Type.CHOICE) -public class Name { - - // This field is the RDNSequence specified in RFC 5280. - @Asn1Field(index = 0, type = Asn1Type.SEQUENCE_OF) - public List relativeDistinguishedNames; -} diff --git a/app/src/main/java/com/android/apksig/internal/x509/RSAPublicKey.java b/app/src/main/java/com/android/apksig/internal/x509/RSAPublicKey.java deleted file mode 100644 index 521e067c26..0000000000 --- a/app/src/main/java/com/android/apksig/internal/x509/RSAPublicKey.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.x509; - -import com.android.apksig.internal.asn1.Asn1Class; -import com.android.apksig.internal.asn1.Asn1Field; -import com.android.apksig.internal.asn1.Asn1Type; - -import java.math.BigInteger; - -/** - * {@code RSAPublicKey} as specified in RFC 3279. - */ -@Asn1Class(type = Asn1Type.SEQUENCE) -public class RSAPublicKey { - @Asn1Field(index = 0, type = Asn1Type.INTEGER) - public BigInteger modulus; - - @Asn1Field(index = 1, type = Asn1Type.INTEGER) - public BigInteger publicExponent; -} diff --git a/app/src/main/java/com/android/apksig/internal/x509/RelativeDistinguishedName.java b/app/src/main/java/com/android/apksig/internal/x509/RelativeDistinguishedName.java deleted file mode 100644 index bb89e8d33a..0000000000 --- a/app/src/main/java/com/android/apksig/internal/x509/RelativeDistinguishedName.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.x509; - -import com.android.apksig.internal.asn1.Asn1Class; -import com.android.apksig.internal.asn1.Asn1Field; -import com.android.apksig.internal.asn1.Asn1Type; - -import java.util.List; - -/** - * {@code RelativeDistinguishedName} as specified in RFC 5280. - */ -@Asn1Class(type = Asn1Type.UNENCODED_CONTAINER) -public class RelativeDistinguishedName { - - @Asn1Field(index = 0, type = Asn1Type.SET_OF) - public List attributes; -} diff --git a/app/src/main/java/com/android/apksig/internal/x509/SubjectPublicKeyInfo.java b/app/src/main/java/com/android/apksig/internal/x509/SubjectPublicKeyInfo.java deleted file mode 100644 index 821523761b..0000000000 --- a/app/src/main/java/com/android/apksig/internal/x509/SubjectPublicKeyInfo.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.x509; - -import com.android.apksig.internal.asn1.Asn1Class; -import com.android.apksig.internal.asn1.Asn1Field; -import com.android.apksig.internal.asn1.Asn1Type; -import com.android.apksig.internal.pkcs7.AlgorithmIdentifier; - -import java.nio.ByteBuffer; - -/** - * {@code SubjectPublicKeyInfo} as specified in RFC 5280. - */ -@Asn1Class(type = Asn1Type.SEQUENCE) -public class SubjectPublicKeyInfo { - @Asn1Field(index = 0, type = Asn1Type.SEQUENCE) - public AlgorithmIdentifier algorithmIdentifier; - - @Asn1Field(index = 1, type = Asn1Type.BIT_STRING) - public ByteBuffer subjectPublicKey; -} diff --git a/app/src/main/java/com/android/apksig/internal/x509/TBSCertificate.java b/app/src/main/java/com/android/apksig/internal/x509/TBSCertificate.java deleted file mode 100644 index 922f52c205..0000000000 --- a/app/src/main/java/com/android/apksig/internal/x509/TBSCertificate.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.x509; - -import com.android.apksig.internal.asn1.Asn1Class; -import com.android.apksig.internal.asn1.Asn1Field; -import com.android.apksig.internal.asn1.Asn1Type; -import com.android.apksig.internal.asn1.Asn1Tagging; -import com.android.apksig.internal.pkcs7.AlgorithmIdentifier; - -import java.math.BigInteger; -import java.nio.ByteBuffer; -import java.util.List; - -/** - * To Be Signed Certificate as specified in RFC 5280. - */ -@Asn1Class(type = Asn1Type.SEQUENCE) -public class TBSCertificate { - - @Asn1Field( - index = 0, - type = Asn1Type.INTEGER, - tagging = Asn1Tagging.EXPLICIT, tagNumber = 0) - public int version; - - @Asn1Field(index = 1, type = Asn1Type.INTEGER) - public BigInteger serialNumber; - - @Asn1Field(index = 2, type = Asn1Type.SEQUENCE) - public AlgorithmIdentifier signatureAlgorithm; - - @Asn1Field(index = 3, type = Asn1Type.CHOICE) - public Name issuer; - - @Asn1Field(index = 4, type = Asn1Type.SEQUENCE) - public Validity validity; - - @Asn1Field(index = 5, type = Asn1Type.CHOICE) - public Name subject; - - @Asn1Field(index = 6, type = Asn1Type.SEQUENCE) - public SubjectPublicKeyInfo subjectPublicKeyInfo; - - @Asn1Field(index = 7, - type = Asn1Type.BIT_STRING, - tagging = Asn1Tagging.IMPLICIT, - optional = true, - tagNumber = 1) - public ByteBuffer issuerUniqueID; - - @Asn1Field(index = 8, - type = Asn1Type.BIT_STRING, - tagging = Asn1Tagging.IMPLICIT, - optional = true, - tagNumber = 2) - public ByteBuffer subjectUniqueID; - - @Asn1Field(index = 9, - type = Asn1Type.SEQUENCE_OF, - tagging = Asn1Tagging.EXPLICIT, - optional = true, - tagNumber = 3) - public List extensions; -} diff --git a/app/src/main/java/com/android/apksig/internal/x509/Time.java b/app/src/main/java/com/android/apksig/internal/x509/Time.java deleted file mode 100644 index def2ee8947..0000000000 --- a/app/src/main/java/com/android/apksig/internal/x509/Time.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.x509; - -import com.android.apksig.internal.asn1.Asn1Class; -import com.android.apksig.internal.asn1.Asn1Field; -import com.android.apksig.internal.asn1.Asn1Type; - -/** - * {@code Time} as specified in RFC 5280. - */ -@Asn1Class(type = Asn1Type.CHOICE) -public class Time { - - @Asn1Field(type = Asn1Type.UTC_TIME) - public String utcTime; - - @Asn1Field(type = Asn1Type.GENERALIZED_TIME) - public String generalizedTime; -} diff --git a/app/src/main/java/com/android/apksig/internal/x509/Validity.java b/app/src/main/java/com/android/apksig/internal/x509/Validity.java deleted file mode 100644 index df9acb3f36..0000000000 --- a/app/src/main/java/com/android/apksig/internal/x509/Validity.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.x509; - -import com.android.apksig.internal.asn1.Asn1Class; -import com.android.apksig.internal.asn1.Asn1Field; -import com.android.apksig.internal.asn1.Asn1Type; - -/** - * {@code Validity} as specified in RFC 5280. - */ -@Asn1Class(type = Asn1Type.SEQUENCE) -public class Validity { - - @Asn1Field(index = 0, type = Asn1Type.CHOICE) - public Time notBefore; - - @Asn1Field(index = 1, type = Asn1Type.CHOICE) - public Time notAfter; -} diff --git a/app/src/main/java/com/android/apksig/internal/zip/CentralDirectoryRecord.java b/app/src/main/java/com/android/apksig/internal/zip/CentralDirectoryRecord.java deleted file mode 100644 index d2f444ddcd..0000000000 --- a/app/src/main/java/com/android/apksig/internal/zip/CentralDirectoryRecord.java +++ /dev/null @@ -1,304 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.zip; - -import com.android.apksig.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 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 GP_FLAGS_OFFSET = 8; - 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 short mGpFlags; - private final short mCompressionMethod; - 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, - short gpFlags, - short compressionMethod, - int lastModificationTime, - int lastModificationDate, - long crc32, - long compressedSize, - long uncompressedSize, - long localFileHeaderOffset, - String name, - int nameSizeBytes) { - mData = data; - mGpFlags = gpFlags; - mCompressionMethod = compressionMethod; - 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 short getGpFlags() { - return mGpFlags; - } - - public short getCompressionMethod() { - return mCompressionMethod; - } - - 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 + GP_FLAGS_OFFSET); - short gpFlags = buf.getShort(); - short compressionMethod = buf.getShort(); - 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, - gpFlags, - compressionMethod, - 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, - mGpFlags, - mCompressionMethod, - 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); - short gpFlags = ZipUtils.GP_FLAG_EFS; // UTF-8 character encoding used for entry name - short compressionMethod = ZipUtils.COMPRESSION_METHOD_DEFLATED; - 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(gpFlags); - result.putShort(compressionMethod); - 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, - gpFlags, - compressionMethod, - 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 { - @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/app/src/main/java/com/android/apksig/internal/zip/EocdRecord.java b/app/src/main/java/com/android/apksig/internal/zip/EocdRecord.java deleted file mode 100644 index 9c531f486d..0000000000 --- a/app/src/main/java/com/android/apksig/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.apksig.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/app/src/main/java/com/android/apksig/internal/zip/LocalFileRecord.java b/app/src/main/java/com/android/apksig/internal/zip/LocalFileRecord.java deleted file mode 100644 index 0a55b1aad7..0000000000 --- a/app/src/main/java/com/android/apksig/internal/zip/LocalFileRecord.java +++ /dev/null @@ -1,537 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.zip; - -import com.android.apksig.internal.util.ByteBufferSink; -import com.android.apksig.util.DataSink; -import com.android.apksig.util.DataSource; -import com.android.apksig.zip.ZipFormatException; -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; - -/** - * ZIP Local File record. - * - *

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 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; - boolean cdDataDescriptorUsed = - (cdRecord.getGpFlags() & ZipUtils.GP_FLAG_DATA_DESCRIPTOR_USED) != 0; - if (dataDescriptorUsed != cdDataDescriptorUsed) { - throw new ZipFormatException( - "Data Descriptor presence mismatch between Local File Header and Central" - + " Directory for entry " + entryName - + ". LFH: " + dataDescriptorUsed + ", CD: " + cdDataDescriptorUsed); - } - 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); - long dataStartOffset = headerStartOffset + HEADER_SIZE_BYTES + nameLength + extraLength; - long dataSize; - boolean compressed = - (cdRecord.getCompressionMethod() != ZipUtils.COMPRESSION_METHOD_STORED); - 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"); - } - } catch (IOException e) { - if (e.getCause() instanceof DataFormatException) { - throw new ZipFormatException("Data of entry " + mName + " malformed", e); - } - throw e; - } - } 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/app/src/main/java/com/android/apksig/internal/zip/ZipUtils.java b/app/src/main/java/com/android/apksig/internal/zip/ZipUtils.java deleted file mode 100644 index 1c2e82cdab..0000000000 --- a/app/src/main/java/com/android/apksig/internal/zip/ZipUtils.java +++ /dev/null @@ -1,385 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.internal.zip; - -import com.android.apksig.apk.ApkFormatException; -import com.android.apksig.internal.util.Pair; -import com.android.apksig.util.DataSource; -import com.android.apksig.zip.ZipFormatException; -import com.android.apksig.zip.ZipSections; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.ArrayList; -import java.util.List; -import java.util.zip.CRC32; -import java.util.zip.Deflater; - -/** - * Assorted ZIP format helpers. - * - *

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 UINT16_MAX_VALUE = 0xffff; - - /** - * Sets the offset of the start of the ZIP Central Directory in the archive. - * - *

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); - } - - /** - * Sets the length of EOCD comment. - * - *

NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian. - */ - public static void updateZipEocdCommentLen(ByteBuffer zipEndOfCentralDirectory) { - assertByteOrderLittleEndian(zipEndOfCentralDirectory); - int commentLen = zipEndOfCentralDirectory.remaining() - ZIP_EOCD_REC_MIN_SIZE; - setUnsignedInt16( - zipEndOfCentralDirectory, - zipEndOfCentralDirectory.position() + ZIP_EOCD_COMMENT_LENGTH_FIELD_OFFSET, - commentLen); - } - - /** - * Returns the offset of the start of the ZIP Central Directory in the archive. - * - *

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. - * - *

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. - * - *

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 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 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 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. - * - *

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; - } - - 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; - } - - public static List parseZipCentralDirectory( - DataSource apk, - ZipSections apkSections) - throws IOException, ApkFormatException { - // Read the ZIP Central Directory - long cdSizeBytes = apkSections.getZipCentralDirectorySizeBytes(); - if (cdSizeBytes > Integer.MAX_VALUE) { - throw new ApkFormatException("ZIP Central Directory too large: " + cdSizeBytes); - } - long cdOffset = apkSections.getZipCentralDirectoryOffset(); - ByteBuffer cd = apk.getByteBuffer(cdOffset, (int) cdSizeBytes); - cd.order(ByteOrder.LITTLE_ENDIAN); - - // Parse the ZIP Central Directory - int expectedCdRecordCount = apkSections.getZipCentralDirectoryRecordCount(); - List 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 ApkFormatException( - "Malformed ZIP 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; - } - - 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/app/src/main/java/com/android/apksig/util/DataSink.java b/app/src/main/java/com/android/apksig/util/DataSink.java deleted file mode 100644 index 5042933f1f..0000000000 --- a/app/src/main/java/com/android/apksig/util/DataSink.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.apksig.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. - * - *

This data sink guarantees to not hold references to the provided buffer after this method - * terminates. - * - * @throws IndexOutOfBoundsException if {@code offset} or {@code length} are negative, or if - * {@code offset + length} is greater than {@code buf.length}. - */ - 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. - * - *

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/app/src/main/java/com/android/apksig/util/DataSinks.java b/app/src/main/java/com/android/apksig/util/DataSinks.java deleted file mode 100644 index d9562d8341..0000000000 --- a/app/src/main/java/com/android/apksig/util/DataSinks.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.util; - -import com.android.apksig.internal.util.ByteArrayDataSink; -import com.android.apksig.internal.util.MessageDigestSink; -import com.android.apksig.internal.util.OutputStreamDataSink; -import com.android.apksig.internal.util.RandomAccessFileDataSink; -import java.io.OutputStream; -import java.io.RandomAccessFile; -import java.security.MessageDigest; - -/** - * 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); - } - - /** - * Returns a {@link DataSink} which forwards data into the provided {@link MessageDigest} - * instances via their {@code update} method. Each {@code MessageDigest} instance receives the - * same data. - */ - public static DataSink asDataSink(MessageDigest... digests) { - return new MessageDigestSink(digests); - } - - /** - * Returns a new in-memory {@link DataSink} which exposes all data consumed so far via the - * {@link DataSource} interface. - */ - public static ReadableDataSink newInMemoryDataSink() { - return new ByteArrayDataSink(); - } - - /** - * Returns a new in-memory {@link DataSink} which exposes all data consumed so far via the - * {@link DataSource} interface. - * - * @param initialCapacity initial capacity in bytes - */ - public static ReadableDataSink newInMemoryDataSink(int initialCapacity) { - return new ByteArrayDataSink(initialCapacity); - } -} diff --git a/app/src/main/java/com/android/apksig/util/DataSource.java b/app/src/main/java/com/android/apksig/util/DataSource.java deleted file mode 100644 index a89a87c5f8..0000000000 --- a/app/src/main/java/com/android/apksig/util/DataSource.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.util; - -import java.io.IOException; -import java.nio.ByteBuffer; - -/** - * Abstract representation of a source of data. - * - *

This abstraction serves three purposes: - *

    - *
  • Transparent handling of different types of sources, such as {@code byte[]}, - * {@link java.nio.ByteBuffer}, {@link java.io.RandomAccessFile}, memory-mapped file.
  • - *
  • Support sources larger than 2 GB. If all sources were smaller than 2 GB, {@code ByteBuffer} - * may have worked as the unifying abstraction.
  • - *
  • Support sources which do not fit into logical memory as a contiguous region.
  • - *
- * - *

There are following ways to obtain a chunk of data from the data source: - *

    - *
  • 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.
  • - *
  • 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.
  • - *
  • 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. - *
  • - *
- */ -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 - * - * @throws IndexOutOfBoundsException if {@code offset} or {@code size} is negative, or if - * {@code offset + size} is greater than {@link #size()}. - */ - 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. - * - *

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 - * - * @throws IndexOutOfBoundsException if {@code offset} or {@code size} is negative, or if - * {@code offset + size} is greater than {@link #size()}. - */ - 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 - * - * @throws IndexOutOfBoundsException if {@code offset} or {@code size} is negative, or if - * {@code offset + size} is greater than {@link #size()}. - */ - 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 - * - * @throws IndexOutOfBoundsException if {@code offset} or {@code size} is negative, or if - * {@code offset + size} is greater than {@link #size()}. - */ - DataSource slice(long offset, long size); -} diff --git a/app/src/main/java/com/android/apksig/util/DataSources.java b/app/src/main/java/com/android/apksig/util/DataSources.java deleted file mode 100644 index 1f0b40b66a..0000000000 --- a/app/src/main/java/com/android/apksig/util/DataSources.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.util; - -import com.android.apksig.internal.util.ByteBufferDataSource; -import com.android.apksig.internal.util.FileChannelDataSource; -import java.io.RandomAccessFile; -import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; - -/** - * 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) { - return asDataSource(file.getChannel()); - } - - /** - * 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) { - return asDataSource(file.getChannel(), offset, size); - } - - /** - * Returns a {@link DataSource} backed by the provided {@link FileChannel}. Changes to the - * file, including changes to size of file, will be visible in the data source. - */ - public static DataSource asDataSource(FileChannel channel) { - if (channel == null) { - throw new NullPointerException(); - } - return new FileChannelDataSource(channel); - } - - /** - * Returns a {@link DataSource} backed by the provided region of the {@link FileChannel}. - * Changes to the file will be visible in the data source. - */ - public static DataSource asDataSource(FileChannel channel, long offset, long size) { - if (channel == null) { - throw new NullPointerException(); - } - return new FileChannelDataSource(channel, offset, size); - } -} diff --git a/app/src/main/java/com/android/apksig/util/ReadableDataSink.java b/app/src/main/java/com/android/apksig/util/ReadableDataSink.java deleted file mode 100644 index ffc3e2d351..0000000000 --- a/app/src/main/java/com/android/apksig/util/ReadableDataSink.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.util; - -/** - * {@link DataSink} which exposes all data consumed so far as a {@link DataSource}. This abstraction - * offers append-only write access and random read access. - */ -public interface ReadableDataSink extends DataSink, DataSource { -} diff --git a/app/src/main/java/com/android/apksig/util/RunnablesExecutor.java b/app/src/main/java/com/android/apksig/util/RunnablesExecutor.java deleted file mode 100644 index 74017f8d8c..0000000000 --- a/app/src/main/java/com/android/apksig/util/RunnablesExecutor.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.util; - -import static java.util.concurrent.TimeUnit.MILLISECONDS; - -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Phaser; -import java.util.concurrent.ThreadPoolExecutor; - -public interface RunnablesExecutor { - static final RunnablesExecutor SINGLE_THREADED = p -> p.createRunnable().run(); - - static final RunnablesExecutor MULTI_THREADED = new RunnablesExecutor() { - private final int PARALLELISM = Math.min(32, Runtime.getRuntime().availableProcessors()); - private final int QUEUE_SIZE = 4; - - @Override - public void execute(RunnablesProvider provider) { - final ExecutorService mExecutor = - new ThreadPoolExecutor(PARALLELISM, PARALLELISM, - 0L, MILLISECONDS, - new ArrayBlockingQueue<>(QUEUE_SIZE), - new ThreadPoolExecutor.CallerRunsPolicy()); - - Phaser tasks = new Phaser(1); - - for (int i = 0; i < PARALLELISM; ++i) { - Runnable task = () -> { - Runnable r = provider.createRunnable(); - r.run(); - tasks.arriveAndDeregister(); - }; - tasks.register(); - mExecutor.execute(task); - } - - // Waiting for the tasks to complete. - tasks.arriveAndAwaitAdvance(); - - mExecutor.shutdownNow(); - } - }; - - void execute(RunnablesProvider provider); -} diff --git a/app/src/main/java/com/android/apksig/util/RunnablesProvider.java b/app/src/main/java/com/android/apksig/util/RunnablesProvider.java deleted file mode 100644 index f96dcfe4d1..0000000000 --- a/app/src/main/java/com/android/apksig/util/RunnablesProvider.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.util; - -public interface RunnablesProvider { - Runnable createRunnable(); -} diff --git a/app/src/main/java/com/android/apksig/zip/ZipFormatException.java b/app/src/main/java/com/android/apksig/zip/ZipFormatException.java deleted file mode 100644 index 6116c0da80..0000000000 --- a/app/src/main/java/com/android/apksig/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.apksig.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/app/src/main/java/com/android/apksig/zip/ZipSections.java b/app/src/main/java/com/android/apksig/zip/ZipSections.java deleted file mode 100644 index 17bce05187..0000000000 --- a/app/src/main/java/com/android/apksig/zip/ZipSections.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apksig.zip; - -import java.nio.ByteBuffer; - -/** - * Base representation of an APK's zip sections containing the central directory's offset, the size - * of the central directory in bytes, the number of records in the central directory, the offset - * of the end of central directory, and a ByteBuffer containing the end of central directory - * contents. - */ -public 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; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/gh/common/util/PackageUtils.java b/app/src/main/java/com/gh/common/util/PackageUtils.java index b04a71c2f2..a725d47cca 100644 --- a/app/src/main/java/com/gh/common/util/PackageUtils.java +++ b/app/src/main/java/com/gh/common/util/PackageUtils.java @@ -299,7 +299,7 @@ public class PackageUtils { } ApkVerifier verifier = new ApkVerifier.Builder(new File(apkFilePath)).build(); - ApkVerifier.Result result = verifier.retrieveV1Signature(); + ApkVerifier.Result result = verifier.retrieveV1SignatureOnly(); return result.getV1SchemeSigners().get(0).getCertificate().getPublicKey().toString(); } catch (Throwable e) { @@ -323,7 +323,7 @@ public class PackageUtils { private static String getV2SignatureFromFile(String apkFilePath) { try { ApkVerifier verifier = new ApkVerifier.Builder(new File(apkFilePath)).build(); - ApkVerifier.Result result = verifier.retrieveV2Signature(); + ApkVerifier.Result result = verifier.retrieveV2SignatureOnly(); return result.getV2SchemeSigners().get(0).getCertificate().getPublicKey().toString(); } catch (Exception e) { diff --git a/dependencies.gradle b/dependencies.gradle index 3dfccfdbe0..0e11e835fe 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -113,6 +113,8 @@ ext { easyFloat = "1.3.4" shapeOfView = "1.4.7" splitties = "3.0.0" + apksig = "1.0.0" + gid = "1.3.0" sentry = "4.3.0"