blob: 4a527adf9abce314d54ed8199dc88b261e9169e1 [file] [log] [blame]
/*
* 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 static android.app.blob.XmlTags.ATTR_CERTIFICATE;
import static android.app.blob.XmlTags.ATTR_PACKAGE;
import static android.app.blob.XmlTags.ATTR_TYPE;
import static android.app.blob.XmlTags.TAG_ALLOWED_PACKAGE;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.content.Context;
import android.content.pm.PackageManager;
import android.util.ArraySet;
import android.util.Base64;
import android.util.DebugUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
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 care of synchronizing access.
*/
class BlobAccessMode {
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {
ACCESS_TYPE_PRIVATE,
ACCESS_TYPE_PUBLIC,
ACCESS_TYPE_SAME_SIGNATURE,
ACCESS_TYPE_ALLOWLIST,
})
@interface AccessType {}
public static final int ACCESS_TYPE_PRIVATE = 1 << 0;
public static final int ACCESS_TYPE_PUBLIC = 1 << 1;
public static final int ACCESS_TYPE_SAME_SIGNATURE = 1 << 2;
public static final int ACCESS_TYPE_ALLOWLIST = 1 << 3;
private int mAccessType = ACCESS_TYPE_PRIVATE;
private final ArraySet<PackageIdentifier> mAllowedPackages = new ArraySet<>();
void allow(BlobAccessMode other) {
if ((other.mAccessType & ACCESS_TYPE_ALLOWLIST) != 0) {
mAllowedPackages.addAll(other.mAllowedPackages);
}
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_ALLOWLIST;
mAllowedPackages.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_ALLOWLIST) == 0) {
return false;
}
return mAllowedPackages.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_ALLOWLIST) != 0) {
for (int i = 0; i < mAllowedPackages.size(); ++i) {
final PackageIdentifier packageIdentifier = mAllowedPackages.valueAt(i);
if (packageIdentifier.packageName.equals(callingPackage)
&& pm.hasSigningCertificate(callingPackage, packageIdentifier.certificate,
PackageManager.CERT_INPUT_SHA256)) {
return true;
}
}
}
return false;
}
int getAccessType() {
return mAccessType;
}
int getAllowedPackagesCount() {
return mAllowedPackages.size();
}
void dump(IndentingPrintWriter fout) {
fout.println("accessType: " + DebugUtils.flagsToString(
BlobAccessMode.class, "ACCESS_TYPE_", mAccessType));
fout.print("Explicitly allowed pkgs:");
if (mAllowedPackages.isEmpty()) {
fout.println(" (Empty)");
} else {
fout.increaseIndent();
for (int i = 0, count = mAllowedPackages.size(); i < count; ++i) {
fout.println(mAllowedPackages.valueAt(i).toString());
}
fout.decreaseIndent();
}
}
void writeToXml(@NonNull XmlSerializer out) throws IOException {
XmlUtils.writeIntAttribute(out, ATTR_TYPE, mAccessType);
for (int i = 0, count = mAllowedPackages.size(); i < count; ++i) {
out.startTag(null, TAG_ALLOWED_PACKAGE);
final PackageIdentifier packageIdentifier = mAllowedPackages.valueAt(i);
XmlUtils.writeStringAttribute(out, ATTR_PACKAGE, packageIdentifier.packageName);
XmlUtils.writeByteArrayAttribute(out, ATTR_CERTIFICATE, packageIdentifier.certificate);
out.endTag(null, TAG_ALLOWED_PACKAGE);
}
}
@NonNull
static BlobAccessMode createFromXml(@NonNull XmlPullParser in)
throws IOException, XmlPullParserException {
final BlobAccessMode blobAccessMode = new BlobAccessMode();
final int accessType = XmlUtils.readIntAttribute(in, ATTR_TYPE);
blobAccessMode.mAccessType = accessType;
final int depth = in.getDepth();
while (XmlUtils.nextElementWithin(in, depth)) {
if (TAG_ALLOWED_PACKAGE.equals(in.getName())) {
final String packageName = XmlUtils.readStringAttribute(in, ATTR_PACKAGE);
final byte[] certificate = XmlUtils.readByteArrayAttribute(in, ATTR_CERTIFICATE);
blobAccessMode.allowPackageAccess(packageName, certificate);
}
}
return blobAccessMode;
}
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));
}
@Override
public String toString() {
return "[" + packageName + ", "
+ Base64.encodeToString(certificate, Base64.NO_WRAP) + "]";
}
}
}