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

Commit bc6b4032 authored by Automerger Merge Worker's avatar Automerger Merge Worker
Browse files

Merge "Updated v4 signature processing." into rvc-dev am: 764e7971 am: b2921cc0 am: d5384678

Change-Id: I88cb91d5e94e49eb8833ca92903f9c0e79f83565
parents 6d7c7897 d5384678
Loading
Loading
Loading
Loading
+0 −1
Original line number Original line Diff line number Diff line
@@ -986,7 +986,6 @@ filegroup {
    srcs: [
    srcs: [
        "core/java/android/os/incremental/IIncrementalService.aidl",
        "core/java/android/os/incremental/IIncrementalService.aidl",
        "core/java/android/os/incremental/IncrementalNewFileParams.aidl",
        "core/java/android/os/incremental/IncrementalNewFileParams.aidl",
        "core/java/android/os/incremental/IncrementalSignature.aidl",
    ],
    ],
    path: "core/java",
    path: "core/java",
}
}
+1 −3
Original line number Original line Diff line number Diff line
@@ -16,8 +16,6 @@


package android.os.incremental;
package android.os.incremental;


import android.os.incremental.IncrementalSignature;

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


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


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


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


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


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


        if (rootHash.length != INCFS_MAX_HASH_SIZE) {
        if (hashingInfo.hashAlgorithm != V4Signature.HASHING_ALGORITHM_SHA256) {
            throw new IOException("rootHash has to be " + INCFS_MAX_HASH_SIZE + " bytes");
            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(
            throw new IOException(
                    "additionalData has to be at most " + INCFS_MAX_ADD_DATA_SIZE + " bytes");
                    "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 Original line Diff line number Diff line
@@ -20,9 +20,12 @@ import android.os.ParcelFileDescriptor;


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


/**
/**
 * V4 signature fields.
 * V4 signature fields.
@@ -31,30 +34,95 @@ import java.io.IOException;
 */
 */
public class V4Signature {
public class V4Signature {
    public static final String EXT = ".idsig";
    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 static final int HASHING_ALGORITHM_SHA256 = 1;
    public final byte[] verityRootHash;
    public static final byte LOG2_BLOCK_SIZE_4096_BYTES = 12;
    public final byte[] v3Digest;

    public final byte[] pkcs7SignatureBlock;
    /**
     * 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.
     * Construct a V4Signature from .idsig file.
     */
     */
    public static V4Signature readFrom(ParcelFileDescriptor pfd) throws IOException {
    public static V4Signature readFrom(ParcelFileDescriptor pfd) throws IOException {
        final ParcelFileDescriptor dupedFd = pfd.dup();
        try (InputStream stream = new ParcelFileDescriptor.AutoCloseInputStream(pfd.dup())) {
        final ParcelFileDescriptor.AutoCloseInputStream fdInputStream =
                new ParcelFileDescriptor.AutoCloseInputStream(dupedFd);
        try (DataInputStream stream = new DataInputStream(fdInputStream)) {
            return readFrom(stream);
            return readFrom(stream);
        }
        }
    }
    }


    /**
    /**
     * Construct a V4Signature from .idsig file.
     * Construct a V4Signature from a byte array.
     */
     */
    public static V4Signature readFrom(byte[] bytes) throws IOException {
    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);
            return readFrom(stream);
        }
        }
    }
    }
@@ -63,51 +131,131 @@ public class V4Signature {
     * Store the V4Signature to a byte-array.
     * Store the V4Signature to a byte-array.
     */
     */
    public byte[] toByteArray() {
    public byte[] toByteArray() {
        try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
        try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
            try (DataOutputStream steam = new DataOutputStream(byteArrayOutputStream)) {
            this.writeTo(stream);
                this.writeTo(steam);
            return stream.toByteArray();
                steam.flush();
            }
            return byteArrayOutputStream.toByteArray();
        } catch (IOException e) {
        } catch (IOException e) {
            return null;
            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 {
    public boolean isVersionSupported() {
        final int version = stream.readInt();
        return this.version == SUPPORTED_VERSION;
        byte[] verityRootHash = readBytes(stream);
        byte[] v3Digest = readBytes(stream);
        byte[] pkcs7SignatureBlock = readBytes(stream);
        return new V4Signature(version, verityRootHash, v3Digest, pkcs7SignatureBlock);
    }
    }


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

    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 {
    private static int readIntLE(InputStream stream) throws IOException {
        stream.writeInt(this.version);
        final byte[] buffer = new byte[4];
        writeBytes(stream, this.verityRootHash);
        readFully(stream, buffer);
        writeBytes(stream, this.v3Digest);
        return ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).getInt();
        writeBytes(stream, this.pkcs7SignatureBlock);
    }
    }


    private static byte[] readBytes(DataInputStream stream) throws IOException {
    private static void writeIntLE(OutputStream stream, int v) throws IOException {
        byte[] result = new byte[stream.readInt()];
        final byte[] buffer = ByteBuffer.wrap(new byte[4]).order(ByteOrder.LITTLE_ENDIAN).putInt(
        stream.read(result);
                v).array();
        return result;
        stream.write(buffer);
    }
    }


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