Search in sources :

Example 1 with LocalFileRecord

use of com.android.apksig.internal.zip.LocalFileRecord in project apksig by venshine.

the class ApkSigner method sign.

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<CentralDirectoryRecord> inputCdRecords = parseZipCentralDirectory(inputCd, inputZipSections);
    List<Hints.PatternWithRange> pinPatterns = extractPinPatterns(inputCdRecords, inputApkLfhSection);
    List<Hints.ByteRange> 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<DefaultApkSignerEngine.SignerConfig> 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<CentralDirectoryRecord> 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<String, CentralDirectoryRecord> outputCdRecordsByName = new HashMap<>(inputCdRecords.size());
    for (final CentralDirectoryRecord inputCdRecord : inputCdRecordsSortedByLfhOffset) {
        String entryName = inputCdRecord.getName();
        if (Hints.PIN_BYTE_RANGE_ZIP_ENTRY_NAME.equals(entryName)) {
            // We'll re-add below if needed.
            continue;
        }
        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");
            }
            // Existing source stamp is handled below as needed.
            continue;
        }
        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<CentralDirectoryRecord> outputCdRecords = new ArrayList<>(inputCdRecords.size() + 10);
    for (CentralDirectoryRecord inputCdRecord : inputCdRecords) {
        String entryName = inputCdRecord.getName();
        CentralDirectoryRecord outputCdRecord = outputCdRecordsByName.get(entryName);
        if (outputCdRecord != null) {
            outputCdRecords.add(outputCdRecord);
        }
    }
    if (lastModifiedDateForNewEntries == -1) {
        // Jan 1 2009 (DOS)
        lastModifiedDateForNewEntries = 0x3a21;
        lastModifiedTimeForNewEntries = 0;
    }
    // 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));
        }
    }
    // 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);
    }
}
Also used : CentralDirectoryRecord(com.android.apksig.internal.zip.CentralDirectoryRecord) HashMap(java.util.HashMap) ArrayList(java.util.ArrayList) ZipFormatException(com.android.apksig.zip.ZipFormatException) ApkFormatException(com.android.apksig.apk.ApkFormatException) ByteBufferDataSource(com.android.apksig.internal.util.ByteBufferDataSource) IOException(java.io.IOException) ByteBuffer(java.nio.ByteBuffer) DataSource(com.android.apksig.util.DataSource) ByteBufferDataSource(com.android.apksig.internal.util.ByteBufferDataSource) LocalFileRecord(com.android.apksig.internal.zip.LocalFileRecord) ApkUtils(com.android.apksig.apk.ApkUtils) ApkSigningBlockNotFoundException(com.android.apksig.apk.ApkSigningBlockNotFoundException)

Aggregations

ApkFormatException (com.android.apksig.apk.ApkFormatException)1 ApkSigningBlockNotFoundException (com.android.apksig.apk.ApkSigningBlockNotFoundException)1 ApkUtils (com.android.apksig.apk.ApkUtils)1 ByteBufferDataSource (com.android.apksig.internal.util.ByteBufferDataSource)1 CentralDirectoryRecord (com.android.apksig.internal.zip.CentralDirectoryRecord)1 LocalFileRecord (com.android.apksig.internal.zip.LocalFileRecord)1 DataSource (com.android.apksig.util.DataSource)1 ZipFormatException (com.android.apksig.zip.ZipFormatException)1 IOException (java.io.IOException)1 ByteBuffer (java.nio.ByteBuffer)1 ArrayList (java.util.ArrayList)1 HashMap (java.util.HashMap)1