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

Commit f5e605a0 authored by Alex Buynytskyy's avatar Alex Buynytskyy
Browse files

Updated v4 signature processing.

Passing to libincfs.so.
Obtaining and verifying, including v3 digest check.

go/apk-v4-signature-format

Test: atest PackageManagerShellCommandTest
Bug: b/151241461
Change-Id: Id61f5716b9f9b55d6ab1ebca5a7ecb1c6e54570a
parent 5bb02f08
Loading
Loading
Loading
Loading
+0 −1
Original line number Diff line number Diff line
@@ -973,7 +973,6 @@ filegroup {
    srcs: [
        "core/java/android/os/incremental/IIncrementalService.aidl",
        "core/java/android/os/incremental/IncrementalNewFileParams.aidl",
        "core/java/android/os/incremental/IncrementalSignature.aidl",
    ],
    path: "core/java",
}
+1 −3
Original line number Diff line number Diff line
@@ -16,8 +16,6 @@

package android.os.incremental;

import android.os.incremental.IncrementalSignature;

/**
 * All the parameters to create a new file on IncFS
 * FileId is a 16 byte-long identifier.
@@ -27,5 +25,5 @@ parcelable IncrementalNewFileParams {
    long size;
    byte[] fileId;
    byte[] metadata;
    @nullable IncrementalSignature signature;
    @nullable byte[] signature;
}
+0 −31
Original line number Diff line number Diff line
/*
 * 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 android.os.incremental;

/** {@hide} */
parcelable IncrementalSignature {
    /*
     * Stable AIDL doesn't support constants, but here's the possible values
     *   const int HASH_ALGO_NONE = 0;
     *   const int HASH_ALGO_SHA256 = 1;
    */

    int hashAlgorithm = 0;
    byte[] rootHash;
    byte[] additionalData;
    byte[] signature;
}
+24 −27
Original line number Diff line number Diff line
@@ -20,8 +20,6 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.RemoteException;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
@@ -180,11 +178,12 @@ public final class IncrementalStorage {
            if (id == null && metadata == null) {
                throw new IOException("File ID and metadata cannot both be null");
            }
            validateV4Signature(v4signatureBytes);
            final IncrementalNewFileParams params = new IncrementalNewFileParams();
            params.size = size;
            params.metadata = (metadata == null ? new byte[0] : metadata);
            params.fileId = idToBytes(id);
            params.signature = parseV4Signature(v4signatureBytes);
            params.signature = v4signatureBytes;
            int res = mService.makeFile(mId, path, params);
            if (res != 0) {
                throw new IOException("makeFile() failed with errno " + -res);
@@ -415,53 +414,51 @@ public final class IncrementalStorage {
        return new UUID(msb, lsb);
    }

    private static final int INCFS_HASH_SHA256 = 1;
    private static final int INCFS_MAX_HASH_SIZE = 32; // SHA256
    private static final int INCFS_MAX_ADD_DATA_SIZE = 128;

    /**
     * Deserialize and validate v4 signature bytes.
     */
    private static IncrementalSignature parseV4Signature(@Nullable byte[] v4signatureBytes)
    private static void validateV4Signature(@Nullable byte[] v4signatureBytes)
            throws IOException {
        if (v4signatureBytes == null || v4signatureBytes.length == 0) {
            return null;
            return;
        }

        final V4Signature signature;
        try (DataInputStream input = new DataInputStream(
                new ByteArrayInputStream(v4signatureBytes))) {
        try {
                signature = V4Signature.readFrom(input);
            signature = V4Signature.readFrom(v4signatureBytes);
        } catch (IOException e) {
            throw new IOException("Failed to read v4 signature:", e);
        }
        }

        if (!signature.isVersionSupported()) {
            throw new IOException("v4 signature version " + signature.version
                    + " is not supported");
        }

        final byte[] rootHash = signature.verityRootHash;
        final byte[] additionalData = signature.v3Digest;
        final byte[] pkcs7Signature = signature.pkcs7SignatureBlock;
        final V4Signature.HashingInfo hashingInfo = V4Signature.HashingInfo.fromByteArray(
                signature.hashingInfo);
        final V4Signature.SigningInfo signingInfo = V4Signature.SigningInfo.fromByteArray(
                signature.signingInfo);

        if (rootHash.length != INCFS_MAX_HASH_SIZE) {
            throw new IOException("rootHash has to be " + INCFS_MAX_HASH_SIZE + " bytes");
        if (hashingInfo.hashAlgorithm != V4Signature.HASHING_ALGORITHM_SHA256) {
            throw new IOException("Unsupported hashAlgorithm: " + hashingInfo.hashAlgorithm);
        }
        if (hashingInfo.log2BlockSize != V4Signature.LOG2_BLOCK_SIZE_4096_BYTES) {
            throw new IOException("Unsupported log2BlockSize: " + hashingInfo.log2BlockSize);
        }
        if (hashingInfo.salt != null && hashingInfo.salt.length > 0) {
            throw new IOException("Unsupported salt: " + hashingInfo.salt);
        }
        if (additionalData.length > INCFS_MAX_ADD_DATA_SIZE) {
        if (hashingInfo.rawRootHash.length != INCFS_MAX_HASH_SIZE) {
            throw new IOException("rawRootHash has to be " + INCFS_MAX_HASH_SIZE + " bytes");
        }
        if (signingInfo.additionalData.length > INCFS_MAX_ADD_DATA_SIZE) {
            throw new IOException(
                    "additionalData has to be at most " + INCFS_MAX_ADD_DATA_SIZE + " bytes");
        }

        IncrementalSignature result = new IncrementalSignature();
        result.hashAlgorithm = INCFS_HASH_SHA256;
        result.rootHash = rootHash;
        result.additionalData = additionalData;
        result.signature = pkcs7Signature;

        return result;
    }

    /**
+190 −42
Original line number Diff line number Diff line
@@ -20,9 +20,12 @@ import android.os.ParcelFileDescriptor;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

/**
 * V4 signature fields.
@@ -31,30 +34,95 @@ import java.io.IOException;
 */
public class V4Signature {
    public static final String EXT = ".idsig";
    public static final int SUPPORTED_VERSION = 1;
    public static final int SUPPORTED_VERSION = 2;

    public final int version;
    public final byte[] verityRootHash;
    public final byte[] v3Digest;
    public final byte[] pkcs7SignatureBlock;
    public static final int HASHING_ALGORITHM_SHA256 = 1;
    public static final byte LOG2_BLOCK_SIZE_4096_BYTES = 12;

    /**
     * IncFS hashing data.
     */
    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;
        }

        /**
         * Constructs HashingInfo from byte array.
         */
        public 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);
        }
    }

    /**
     * V4 signature data.
     */
    public static class SigningInfo {
        public final byte[] v3Digest;  // 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[] v3Digest, byte[] certificate, byte[] additionalData,
                byte[] publicKey, int signatureAlgorithmId, byte[] signature) {
            this.v3Digest = v3Digest;
            this.certificate = certificate;
            this.additionalData = additionalData;
            this.publicKey = publicKey;
            this.signatureAlgorithmId = signatureAlgorithmId;
            this.signature = signature;
        }

        /**
         * Constructs SigningInfo from byte array.
         */
        public static SigningInfo fromByteArray(byte[] bytes) throws IOException {
            ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
            byte[] v3Digest = 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(v3Digest, certificate, additionalData, publicKey,
                    signatureAlgorithmId, signature);
        }
    }

    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.

    /**
     * Construct a V4Signature from .idsig file.
     */
    public static V4Signature readFrom(ParcelFileDescriptor pfd) throws IOException {
        final ParcelFileDescriptor dupedFd = pfd.dup();
        final ParcelFileDescriptor.AutoCloseInputStream fdInputStream =
                new ParcelFileDescriptor.AutoCloseInputStream(dupedFd);
        try (DataInputStream stream = new DataInputStream(fdInputStream)) {
        try (InputStream stream = new ParcelFileDescriptor.AutoCloseInputStream(pfd.dup())) {
            return readFrom(stream);
        }
    }

    /**
     * Construct a V4Signature from .idsig file.
     * Construct a V4Signature from a byte array.
     */
    public static V4Signature readFrom(byte[] bytes) throws IOException {
        try (DataInputStream stream = new DataInputStream(new ByteArrayInputStream(bytes))) {
        try (InputStream stream = new ByteArrayInputStream(bytes)) {
            return readFrom(stream);
        }
    }
@@ -63,51 +131,131 @@ public class V4Signature {
     * Store the V4Signature to a byte-array.
     */
    public byte[] toByteArray() {
        try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
            try (DataOutputStream steam = new DataOutputStream(byteArrayOutputStream)) {
                this.writeTo(steam);
                steam.flush();
            }
            return byteArrayOutputStream.toByteArray();
        try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
            this.writeTo(stream);
            return stream.toByteArray();
        } catch (IOException e) {
            return null;
        }
    }

    boolean isVersionSupported() {
        return this.version == SUPPORTED_VERSION;
    /**
     * Combines necessary data to a signed data blob.
     * The blob can be validated against signingInfo.signature.
     *
     * @param fileSize - size of the signed file (APK)
     */
    public 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.v3Digest) + 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.v3Digest);
        writeBytes(buffer, signingInfo.certificate);
        writeBytes(buffer, signingInfo.additionalData);
        return buffer.array();
    }

    static V4Signature readFrom(DataInputStream stream) throws IOException {
        final int version = stream.readInt();
        byte[] verityRootHash = readBytes(stream);
        byte[] v3Digest = readBytes(stream);
        byte[] pkcs7SignatureBlock = readBytes(stream);
        return new V4Signature(version, verityRootHash, v3Digest, pkcs7SignatureBlock);
    public boolean isVersionSupported() {
        return this.version == SUPPORTED_VERSION;
    }

    V4Signature(int version, byte[] verityRootHash, byte[] v3Digest, byte[] pkcs7SignatureBlock) {
    private V4Signature(int version, byte[] hashingInfo, byte[] signingInfo) {
        this.version = version;
        this.verityRootHash = verityRootHash;
        this.v3Digest = v3Digest;
        this.pkcs7SignatureBlock = pkcs7SignatureBlock;
        this.hashingInfo = hashingInfo;
        this.signingInfo = signingInfo;
    }

    private static V4Signature readFrom(InputStream stream) throws IOException {
        final int version = readIntLE(stream);
        final byte[] hashingInfo = readBytes(stream);
        final byte[] signingInfo = readBytes(stream);
        return new V4Signature(version, hashingInfo, signingInfo);
    }

    private void writeTo(OutputStream stream) throws IOException {
        writeIntLE(stream, this.version);
        writeBytes(stream, this.hashingInfo);
        writeBytes(stream, this.signingInfo);
    }

    // Utility methods.
    private static int bytesSize(byte[] bytes) {
        return 4/*length*/ + (bytes == null ? 0 : bytes.length);
    }

    private 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;
        }
    }

    void writeTo(DataOutputStream stream) throws IOException {
        stream.writeInt(this.version);
        writeBytes(stream, this.verityRootHash);
        writeBytes(stream, this.v3Digest);
        writeBytes(stream, this.pkcs7SignatureBlock);
    private 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();
    }

    private static byte[] readBytes(DataInputStream stream) throws IOException {
        byte[] result = new byte[stream.readInt()];
        stream.read(result);
        return result;
    private 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);
    }

    private static void writeBytes(DataOutputStream stream, byte[] bytes) throws IOException {
        stream.writeInt(bytes.length);
    private 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;
        }
    }

    private 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;
    }

    private static void writeBytes(OutputStream stream, byte[] bytes) throws IOException {
        if (bytes == null) {
            writeIntLE(stream, 0);
            return;
        }
        writeIntLE(stream, bytes.length);
        stream.write(bytes);
    }

    private static void writeBytes(ByteBuffer buffer, byte[] bytes) {
        if (bytes == null) {
            buffer.putInt(0);
            return;
        }
        buffer.putInt(bytes.length);
        buffer.put(bytes);
    }
}
Loading