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

Commit ce7f6e89 authored by Sudheer Shanka's avatar Sudheer Shanka Committed by Android (Google) Code Review
Browse files

Merge "Implement BlobStoreManagerService."

parents 0e12abac ab1d4160
Loading
Loading
Loading
Loading
+24 −0
Original line number Diff line number Diff line
@@ -22,6 +22,9 @@ import android.os.Parcelable;

import com.android.internal.util.Preconditions;

import java.util.Arrays;
import java.util.Objects;

/**
 * An identifier to represent a blob.
 */
@@ -173,6 +176,27 @@ public final class BlobHandle implements Parcelable {
        dest.writeString(tag);
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null || !(obj instanceof BlobHandle)) {
            return false;
        }
        final BlobHandle other = (BlobHandle) obj;
        return this.algorithm.equals(other.algorithm)
                && Arrays.equals(this.digest, other.digest)
                && this.label.equals(other.label)
                && this.expiryTimeMillis == other.expiryTimeMillis
                && this.tag.equals(tag);
    }

    @Override
    public int hashCode() {
        return Objects.hash(algorithm, Arrays.hashCode(digest), label, expiryTimeMillis, tag);
    }

    public static final @NonNull Creator<BlobHandle> CREATOR = new Creator<BlobHandle>() {
        @Override
        public @NonNull BlobHandle createFromParcel(@NonNull Parcel source) {
+104 −2
Original line number Diff line number Diff line
@@ -45,6 +45,11 @@ import java.util.function.Consumer;
 */
@SystemService(Context.BLOB_STORE_SERVICE)
public class BlobStoreManager {
    /** @hide */
    public static final int COMMIT_RESULT_SUCCESS = 0;
    /** @hide */
    public static final int COMMIT_RESULT_ERROR = 1;

    private final Context mContext;
    private final IBlobStoreManager mService;

@@ -102,7 +107,28 @@ public class BlobStoreManager {
     */
    public @NonNull Session openSession(@IntRange(from = 1) long sessionId) throws IOException {
        try {
            return new Session(mService.openSession(sessionId));
            return new Session(mService.openSession(sessionId, mContext.getOpPackageName()));
        } catch (ParcelableException e) {
            e.maybeRethrow(IOException.class);
            throw new RuntimeException(e);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Delete an existing session and any data that was written to that session so far.
     *
     * @param sessionId a unique id obtained via {@link #createSession(BlobHandle)} that
     *                  represents a particular session.
     *
     * @throws IOException when there is an I/O error while deleting the session.
     * @throws SecurityException when the caller does not own the session, or
     *                           the session does not exist or is invalid.
     */
    public void deleteSession(@IntRange(from = 1) long sessionId) throws IOException {
        try {
            mService.deleteSession(sessionId, mContext.getOpPackageName());
        } catch (ParcelableException e) {
            e.maybeRethrow(IOException.class);
            throw new RuntimeException(e);
@@ -142,6 +168,9 @@ public class BlobStoreManager {
     * <p> Any active leases will be automatically released when the blob's expiry time
     * ({@link BlobHandle#getExpiryTimeMillis()}) is elapsed.
     *
     * <p> This lease information is persisted and calling this more than once will result in
     * latest lease overriding any previous lease.
     *
     * @param blobHandle the {@link BlobHandle} representing the blob that the caller wants to
     *                   acquire a lease for.
     * @param descriptionResId the resource id for a short description string that can be surfaced
@@ -190,6 +219,9 @@ public class BlobStoreManager {
     * <p> Any active leases will be automatically released when the blob's expiry time
     * ({@link BlobHandle#getExpiryTimeMillis()}) is elapsed.
     *
     * <p> This lease information is persisted and calling this more than once will result in
     * latest lease overriding any previous lease.
     *
     * @param blobHandle the {@link BlobHandle} representing the blob that the caller wants to
     *                   acquire a lease for.
     * @param descriptionResId the resource id for a short description string that can be surfaced
@@ -279,7 +311,9 @@ public class BlobStoreManager {
        public @NonNull ParcelFileDescriptor openWrite(@BytesLong long offsetBytes,
                @BytesLong long lengthBytes) throws IOException {
            try {
                return mSession.openWrite(offsetBytes, lengthBytes);
                final ParcelFileDescriptor pfd = mSession.openWrite(offsetBytes, lengthBytes);
                pfd.seekTo(offsetBytes);
                return pfd;
            } catch (ParcelableException e) {
                e.maybeRethrow(IOException.class);
                throw new RuntimeException(e);
@@ -375,6 +409,31 @@ public class BlobStoreManager {
            }
        }

        /**
         * Returns {@code true} if access has been allowed for a {@code packageName} using either
         * {@link #allowPackageAccess(String, byte[])}.
         * Otherwise, {@code false}.
         *
         * @param packageName the name of the package to check the access for.
         * @param certificate the input bytes representing a certificate of type
         *                    {@link android.content.pm.PackageManager#CERT_INPUT_SHA256}.
         *
         * @throws IOException when there is an I/O error while getting the access type.
         * @throws IllegalStateException when the caller tries to get access type from a session
         *                               which is closed or abandoned.
         */
        public boolean isPackageAccessAllowed(@NonNull String packageName,
                @NonNull byte[] certificate) throws IOException {
            try {
                return mSession.isPackageAccessAllowed(packageName, certificate);
            } catch (ParcelableException e) {
                e.maybeRethrow(IOException.class);
                throw new RuntimeException(e);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }

        /**
         * Allow packages which are signed with the same certificate as the caller to access this
         * blob data once it is committed using a {@link BlobHandle} representing the blob.
@@ -398,6 +457,26 @@ public class BlobStoreManager {
            }
        }

        /**
         * Returns {@code true} if access has been allowed for packages signed with the same
         * certificate as the caller by using {@link #allowSameSignatureAccess()}.
         * Otherwise, {@code false}.
         *
         * @throws IOException when there is an I/O error while getting the access type.
         * @throws IllegalStateException when the caller tries to get access type from a session
         *                               which is closed or abandoned.
         */
        public boolean isSameSignatureAccessAllowed() throws IOException {
            try {
                return mSession.isSameSignatureAccessAllowed();
            } catch (ParcelableException e) {
                e.maybeRethrow(IOException.class);
                throw new RuntimeException(e);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }

        /**
         * Allow any app on the device to access this blob data once it is committed using
         * a {@link BlobHandle} representing the blob.
@@ -426,6 +505,25 @@ public class BlobStoreManager {
            }
        }

        /**
         * Returns {@code true} if public access has been allowed by using
         * {@link #allowPublicAccess()}. Otherwise, {@code false}.
         *
         * @throws IOException when there is an I/O error while getting the access type.
         * @throws IllegalStateException when the caller tries to get access type from a session
         *                               which is closed or abandoned.
         */
        public boolean isPublicAccessAllowed() throws IOException {
            try {
                return mSession.isPublicAccessAllowed();
            } catch (ParcelableException e) {
                e.maybeRethrow(IOException.class);
                throw new RuntimeException(e);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }

        /**
         * Commit the file that was written so far to this session to the blob store maintained by
         * the system.
@@ -439,6 +537,10 @@ public class BlobStoreManager {
         * {@link BlobHandle#createWithSha256(byte[], CharSequence, long, String)}  BlobHandle}
         * associated with this session.
         *
         * <p> Committing the same data more than once will result in replacing the corresponding
         * access mode (via calling one of {@link #allowPackageAccess(String, byte[])},
         * {@link #allowSameSignatureAccess()}, etc) with the latest one.
         *
         * @param executor the executor on which result callback will be invoked.
         * @param resultCallback a callback to receive the commit result. when the result is
         *                       {@code 0}, it indicates success. Otherwise, failure.
+2 −1
Original line number Diff line number Diff line
@@ -21,8 +21,9 @@ import android.app.blob.IBlobStoreSession;
/** {@hide} */
interface IBlobStoreManager {
    long createSession(in BlobHandle handle, in String packageName);
    IBlobStoreSession openSession(long sessionId);
    IBlobStoreSession openSession(long sessionId, in String packageName);
    ParcelFileDescriptor openBlob(in BlobHandle handle, in String packageName);
    void deleteSession(long sessionId, in String packageName);

    void acquireLease(in BlobHandle handle, int descriptionResId, long leaseTimeout,
            in String packageName);
+4 −0
Original line number Diff line number Diff line
@@ -26,6 +26,10 @@ interface IBlobStoreSession {
    void allowSameSignatureAccess();
    void allowPublicAccess();

    boolean isPackageAccessAllowed(in String packageName, in byte[] certificate);
    boolean isSameSignatureAccessAllowed();
    boolean isPublicAccessAllowed();

    long getSize();
    void close();
    void abandon();
+147 −0
Original line number Diff line number Diff line
/*
 * Copyright 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 com.android.server.blob;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.content.Context;
import android.content.pm.PackageManager;
import android.util.ArraySet;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.Objects;

/**
 * Class for representing how a blob can be shared.
 *
 * Note that this class is not thread-safe, callers need to take of synchronizing access.
 */
class BlobAccessMode {
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(flag = true, value = {
            ACCESS_TYPE_PRIVATE,
            ACCESS_TYPE_PUBLIC,
            ACCESS_TYPE_SAME_SIGNATURE,
            ACCESS_TYPE_WHITELIST,
    })
    @interface AccessType {}
    static final int ACCESS_TYPE_PRIVATE = 1 << 0;
    static final int ACCESS_TYPE_PUBLIC = 1 << 1;
    static final int ACCESS_TYPE_SAME_SIGNATURE = 1 << 2;
    static final int ACCESS_TYPE_WHITELIST = 1 << 3;

    private int mAccessType = ACCESS_TYPE_PRIVATE;

    private final ArraySet<PackageIdentifier> mWhitelistedPackages = new ArraySet<>();

    void allow(BlobAccessMode other) {
        if ((other.mAccessType & ACCESS_TYPE_WHITELIST) != 0) {
            mWhitelistedPackages.addAll(other.mWhitelistedPackages);
        }
        mAccessType |= other.mAccessType;
    }

    void allowPublicAccess() {
        mAccessType |= ACCESS_TYPE_PUBLIC;
    }

    void allowSameSignatureAccess() {
        mAccessType |= ACCESS_TYPE_SAME_SIGNATURE;
    }

    void allowPackageAccess(@NonNull String packageName, @NonNull byte[] certificate) {
        mAccessType |= ACCESS_TYPE_WHITELIST;
        mWhitelistedPackages.add(PackageIdentifier.create(packageName, certificate));
    }

    boolean isPublicAccessAllowed() {
        return (mAccessType & ACCESS_TYPE_PUBLIC) != 0;
    }

    boolean isSameSignatureAccessAllowed() {
        return (mAccessType & ACCESS_TYPE_SAME_SIGNATURE) != 0;
    }

    boolean isPackageAccessAllowed(@NonNull String packageName, @NonNull byte[] certificate) {
        if ((mAccessType & ACCESS_TYPE_WHITELIST) == 0) {
            return false;
        }
        return mWhitelistedPackages.contains(PackageIdentifier.create(packageName, certificate));
    }

    boolean isAccessAllowedForCaller(Context context,
            @NonNull String callingPackage, @NonNull String committerPackage) {
        if ((mAccessType & ACCESS_TYPE_PUBLIC) != 0) {
            return true;
        }

        final PackageManager pm = context.getPackageManager();
        if ((mAccessType & ACCESS_TYPE_SAME_SIGNATURE) != 0) {
            if (pm.checkSignatures(committerPackage, callingPackage)
                    == PackageManager.SIGNATURE_MATCH) {
                return true;
            }
        }

        if ((mAccessType & ACCESS_TYPE_WHITELIST) != 0) {
            for (int i = 0; i < mWhitelistedPackages.size(); ++i) {
                final PackageIdentifier packageIdentifier = mWhitelistedPackages.valueAt(i);
                if (packageIdentifier.packageName.equals(callingPackage)
                        && pm.hasSigningCertificate(callingPackage, packageIdentifier.certificate,
                                PackageManager.CERT_INPUT_SHA256)) {
                    return true;
                }
            }
        }

        return false;
    }

    private static final class PackageIdentifier {
        public final String packageName;
        public final byte[] certificate;

        private PackageIdentifier(@NonNull String packageName, @NonNull byte[] certificate) {
            this.packageName = packageName;
            this.certificate = certificate;
        }

        public static PackageIdentifier create(@NonNull String packageName,
                @NonNull byte[] certificate) {
            return new PackageIdentifier(packageName, certificate);
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null || !(obj instanceof PackageIdentifier)) {
                return false;
            }
            final PackageIdentifier other = (PackageIdentifier) obj;
            return this.packageName.equals(other.packageName)
                    && Arrays.equals(this.certificate, other.certificate);
        }

        @Override
        public int hashCode() {
            return Objects.hash(packageName, Arrays.hashCode(certificate));
        }
    }
}
Loading