Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d81beca2 authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "APK signer primitive."

parents 997a6cd1 819e8485
Loading
Loading
Loading
Loading
+711 −0

File added.

Preview size limit exceeded, changes collapsed.

+5 −4
Original line number Diff line number Diff line
@@ -33,9 +33,9 @@ import com.android.apksigner.core.util.DataSource;
 * <p><h3>Operating Model</h3>
 *
 * The abstract operating model is that there is an input APK which is being signed, thus producing
 * an output APK. In reality, there may be just an output APK being built from scratch, or the input APK and
 * the output APK may be the same file. Because this engine does not deal with reading and writing
 * files, it can handle all of these scenarios.
 * an output APK. In reality, there may be just an output APK being built from scratch, or the input
 * APK and the output APK may be the same file. Because this engine does not deal with reading and
 * writing files, it can handle all of these scenarios.
 *
 * <p>The engine is stateful and thus cannot be used for signing multiple APKs. However, once
 * the engine signed an APK, the engine can be used to re-sign the APK after it has been modified.
@@ -119,9 +119,10 @@ public interface ApkSignerEngine extends Closeable {
     * @param apkSigningBlock APK signing block of the input APK. The provided data source is
     *        guaranteed to not be used by the engine after this method terminates.
     *
     * @throws IOException if an I/O error occurs while reading the APK Signing Block
     * @throws IllegalStateException if this engine is closed
     */
    void inputApkSigningBlock(DataSource apkSigningBlock) throws IllegalStateException;
    void inputApkSigningBlock(DataSource apkSigningBlock) throws IOException, IllegalStateException;

    /**
     * Indicates to this engine that the specified JAR entry was encountered in the input APK.
+6 −15
Original line number Diff line number Diff line
@@ -47,7 +47,7 @@ import com.android.apksigner.core.internal.util.AndroidSdkVersion;
import com.android.apksigner.core.internal.util.InclusiveIntRange;
import com.android.apksigner.core.internal.util.MessageDigestSink;
import com.android.apksigner.core.internal.zip.CentralDirectoryRecord;
import com.android.apksigner.core.internal.zip.LocalFileHeader;
import com.android.apksigner.core.internal.zip.LocalFileRecord;
import com.android.apksigner.core.util.DataSource;
import com.android.apksigner.core.zip.ZipFormatException;

@@ -187,10 +187,7 @@ public abstract class V1SchemeVerifier {

            // Parse the JAR manifest and check that all JAR entries it references exist in the APK.
            byte[] manifestBytes =
                    LocalFileHeader.getUncompressedData(
                            apk, 0,
                            manifestEntry,
                            cdStartOffset);
                    LocalFileRecord.getUncompressedData(apk, manifestEntry, cdStartOffset);
            Map<String, ManifestParser.Section> entryNameToManifestSection = null;
            ManifestParser manifest = new ManifestParser(manifestBytes);
            ManifestParser.Section manifestMainSection = manifest.readSection();
@@ -411,15 +408,9 @@ public abstract class V1SchemeVerifier {
                DataSource apk, long cdStartOffset, int minSdkVersion, int maxSdkVersion)
                        throws IOException, ZipFormatException, NoSuchAlgorithmException {
            byte[] sigBlockBytes =
                    LocalFileHeader.getUncompressedData(
                            apk, 0,
                            mSignatureBlockEntry,
                            cdStartOffset);
                    LocalFileRecord.getUncompressedData(apk, mSignatureBlockEntry, cdStartOffset);
            mSigFileBytes =
                    LocalFileHeader.getUncompressedData(
                            apk, 0,
                            mSignatureFileEntry,
                            cdStartOffset);
                    LocalFileRecord.getUncompressedData(apk, mSignatureFileEntry, cdStartOffset);
            PKCS7 sigBlock;
            try {
                sigBlock = new PKCS7(sigBlockBytes);
@@ -1412,8 +1403,8 @@ public abstract class V1SchemeVerifier {
            }

            try {
                LocalFileHeader.sendUncompressedData(
                        apk, 0,
                LocalFileRecord.outputUncompressedData(
                        apk,
                        cdRecord,
                        cdOffsetInApk,
                        new MessageDigestSink(mds));
+33 −25
Original line number Diff line number Diff line
@@ -553,38 +553,35 @@ public abstract class V2SchemeVerifier {
    private static SignatureInfo findSignature(
            DataSource apk, ApkUtils.ZipSections zipSections, Result result)
                    throws IOException, SignatureNotFoundException {
        long centralDirStartOffset = zipSections.getZipCentralDirectoryOffset();
        long centralDirEndOffset =
                centralDirStartOffset + zipSections.getZipCentralDirectorySizeBytes();
        long eocdStartOffset = zipSections.getZipEndOfCentralDirectoryOffset();
        if (centralDirEndOffset != eocdStartOffset) {
            throw new SignatureNotFoundException(
                    "ZIP Central Directory is not immediately followed by End of Central Directory"
                            + ". CD end: " + centralDirEndOffset
                            + ", EoCD start: " + eocdStartOffset);
        }

        // Find the APK Signing Block. The block immediately precedes the Central Directory.
        ByteBuffer eocd = zipSections.getZipEndOfCentralDirectory();
        Pair<ByteBuffer, Long> apkSigningBlockAndOffset =
                findApkSigningBlock(apk, centralDirStartOffset);
        ByteBuffer apkSigningBlock = apkSigningBlockAndOffset.getFirst();
        Pair<DataSource, Long> apkSigningBlockAndOffset = findApkSigningBlock(apk, zipSections);
        DataSource apkSigningBlock = apkSigningBlockAndOffset.getFirst();
        long apkSigningBlockOffset = apkSigningBlockAndOffset.getSecond();
        ByteBuffer apkSigningBlockBuf =
                apkSigningBlock.getByteBuffer(0, (int) apkSigningBlock.size());
        apkSigningBlockBuf.order(ByteOrder.LITTLE_ENDIAN);

        // Find the APK Signature Scheme v2 Block inside the APK Signing Block.
        ByteBuffer apkSignatureSchemeV2Block =
                findApkSignatureSchemeV2Block(apkSigningBlock, result);
                findApkSignatureSchemeV2Block(apkSigningBlockBuf, result);

        return new SignatureInfo(
                apkSignatureSchemeV2Block,
                apkSigningBlockOffset,
                centralDirStartOffset,
                eocdStartOffset,
                zipSections.getZipCentralDirectoryOffset(),
                zipSections.getZipEndOfCentralDirectoryOffset(),
                eocd);
    }

    private static Pair<ByteBuffer, Long> findApkSigningBlock(
            DataSource apk, long centralDirOffset) throws IOException, SignatureNotFoundException {
    /**
     * Returns the APK Signing Block and its offset in the provided APK.
     *
     * @throws SignatureNotFoundException if the APK does not contain an APK Signing Block
     */
    public static Pair<DataSource, Long> findApkSigningBlock(
            DataSource apk, ApkUtils.ZipSections zipSections)
                    throws IOException, SignatureNotFoundException {
        // FORMAT:
        // OFFSET       DATA TYPE  DESCRIPTION
        // * @+0  bytes uint64:    size in bytes (excluding this field)
@@ -592,15 +589,26 @@ public abstract class V2SchemeVerifier {
        // * @-24 bytes uint64:    size in bytes (same as the one above)
        // * @-16 bytes uint128:   magic

        if (centralDirOffset < APK_SIG_BLOCK_MIN_SIZE) {
        long centralDirStartOffset = zipSections.getZipCentralDirectoryOffset();
        long centralDirEndOffset =
                centralDirStartOffset + zipSections.getZipCentralDirectorySizeBytes();
        long eocdStartOffset = zipSections.getZipEndOfCentralDirectoryOffset();
        if (centralDirEndOffset != eocdStartOffset) {
            throw new SignatureNotFoundException(
                    "ZIP Central Directory is not immediately followed by End of Central Directory"
                            + ". CD end: " + centralDirEndOffset
                            + ", EoCD start: " + eocdStartOffset);
        }

        if (centralDirStartOffset < APK_SIG_BLOCK_MIN_SIZE) {
            throw new SignatureNotFoundException(
                    "APK too small for APK Signing Block. ZIP Central Directory offset: "
                            + centralDirOffset);
                            + 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(centralDirOffset - 24, 24);
        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)) {
@@ -615,12 +623,12 @@ public abstract class V2SchemeVerifier {
                    "APK Signing Block size out of range: " + apkSigBlockSizeInFooter);
        }
        int totalSize = (int) (apkSigBlockSizeInFooter + 8);
        long apkSigBlockOffset = centralDirOffset - totalSize;
        long apkSigBlockOffset = centralDirStartOffset - totalSize;
        if (apkSigBlockOffset < 0) {
            throw new SignatureNotFoundException(
                    "APK Signing Block offset out of range: " + apkSigBlockOffset);
        }
        ByteBuffer apkSigBlock = apk.getByteBuffer(apkSigBlockOffset, totalSize);
        ByteBuffer apkSigBlock = apk.getByteBuffer(apkSigBlockOffset, 8);
        apkSigBlock.order(ByteOrder.LITTLE_ENDIAN);
        long apkSigBlockSizeInHeader = apkSigBlock.getLong(0);
        if (apkSigBlockSizeInHeader != apkSigBlockSizeInFooter) {
@@ -628,7 +636,7 @@ public abstract class V2SchemeVerifier {
                    "APK Signing Block sizes in header and footer do not match: "
                            + apkSigBlockSizeInHeader + " vs " + apkSigBlockSizeInFooter);
        }
        return Pair.of(apkSigBlock, apkSigBlockOffset);
        return Pair.of(apk.slice(apkSigBlockOffset, totalSize), apkSigBlockOffset);
    }

    private static ByteBuffer findApkSignatureSchemeV2Block(
+87 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.apksigner.core.internal.util;

import com.android.apksigner.core.util.DataSink;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
 * {@link DataSink} which outputs received data into the associated file, sequentially.
 */
public class RandomAccessFileDataSink implements DataSink {

    private final RandomAccessFile mFile;
    private final FileChannel mFileChannel;
    private long mPosition;

    /**
     * Constructs a new {@code RandomAccessFileDataSink} which stores output starting from the
     * beginning of the provided file.
     */
    public RandomAccessFileDataSink(RandomAccessFile file) {
        this(file, 0);
    }

    /**
     * Constructs a new {@code RandomAccessFileDataSink} which stores output starting from the
     * specified position of the provided file.
     */
    public RandomAccessFileDataSink(RandomAccessFile file, long startPosition) {
        if (file == null) {
            throw new NullPointerException("file == null");
        }
        if (startPosition < 0) {
            throw new IllegalArgumentException("startPosition: " + startPosition);
        }
        mFile = file;
        mFileChannel = file.getChannel();
        mPosition = startPosition;
    }

    @Override
    public void consume(byte[] buf, int offset, int length) throws IOException {
        if (length == 0) {
            return;
        }

        synchronized (mFile) {
            mFile.seek(mPosition);
            mFile.write(buf, offset, length);
            mPosition += length;
        }
    }

    @Override
    public void consume(ByteBuffer buf) throws IOException {
        int length = buf.remaining();
        if (length == 0) {
            return;
        }

        synchronized (mFile) {
            mFile.seek(mPosition);
            while (buf.hasRemaining()) {
                mFileChannel.write(buf);
            }
            mPosition += length;
        }
    }
}
Loading