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

Commit ab1d4160 authored by Sudheer Shanka's avatar Sudheer Shanka
Browse files

Implement BlobStoreManagerService.

This change implements the following APIs with a few TODOs:
- BlobStoreManager.{create,open,delete}Sesison()
- BlobStoreManager.Session.{openWrite,openRead,close,abandon}()
- BlobStoreManager.Session.allow*Access()
- BlobStoreManager.Session.commit()
- BlobStoreManager.openBlob()
- BlobStoreManager.{acquire,release}Lease()

Bug: 143559646
Test: atest cts/tests/BlobStore/src/com/android/cts/blob/BlobStoreManagerTest.java
Change-Id: Id4eb8269f013b5c0ffca63c8251a2553e818091d
parent 5cc8e204
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