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

Commit 69c8c38f authored by Alex Klyubin's avatar Alex Klyubin Committed by android-build-merger
Browse files

Merge \"APK signer primitive.\"

am: d81beca2

Change-Id: I9570df7b5f7a70b4fdd04cbbdeae80d3e5bf9616
parents 9223ab05 d81beca2
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