1 /* 2 * Copyright 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.appsearch.external.localstorage.visibilitystore; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.app.appsearch.AppSearchSchema; 22 import android.app.appsearch.GenericDocument; 23 import android.app.appsearch.InternalVisibilityConfig; 24 import android.app.appsearch.PackageIdentifier; 25 import android.app.appsearch.SchemaVisibilityConfig; 26 import android.app.appsearch.VisibilityPermissionConfig; 27 import android.util.ArraySet; 28 import android.util.Log; 29 30 import com.google.android.appsearch.proto.AndroidVOverlayProto; 31 import com.google.android.appsearch.proto.PackageIdentifierProto; 32 import com.google.android.appsearch.proto.VisibilityConfigProto; 33 import com.google.android.appsearch.proto.VisibleToPermissionProto; 34 import com.google.protobuf.ByteString; 35 import com.google.protobuf.InvalidProtocolBufferException; 36 37 import java.util.List; 38 import java.util.Objects; 39 import java.util.Set; 40 41 /** 42 * Utilities for working with {@link VisibilityChecker} and {@link VisibilityStore}. 43 * 44 * @hide 45 */ 46 public class VisibilityToDocumentConverter { 47 private static final String TAG = "AppSearchVisibilityToDo"; 48 49 /** 50 * The Schema type for documents that hold AppSearch's metadata, such as visibility settings. 51 */ 52 public static final String VISIBILITY_DOCUMENT_SCHEMA_TYPE = "VisibilityType"; 53 54 /** Namespace of documents that contain visibility settings */ 55 public static final String VISIBILITY_DOCUMENT_NAMESPACE = ""; 56 57 /** 58 * The Schema type for the Android V visibility setting overlay documents, that allow for 59 * additional visibility settings. 60 */ 61 public static final String ANDROID_V_OVERLAY_SCHEMA_TYPE = "AndroidVOverlayType"; 62 63 /** Namespace of documents that contain Android V visibility setting overlay documents */ 64 public static final String ANDROID_V_OVERLAY_NAMESPACE = "androidVOverlay"; 65 66 /** Property that holds the serialized {@link AndroidVOverlayProto}. */ 67 public static final String VISIBILITY_PROTO_SERIALIZE_PROPERTY = 68 "visibilityProtoSerializeProperty"; 69 70 /** 71 * Property that holds the list of platform-hidden schemas, as part of the visibility settings. 72 */ 73 private static final String NOT_DISPLAYED_BY_SYSTEM_PROPERTY = "notPlatformSurfaceable"; 74 75 /** Property that holds the package name that can access a schema. */ 76 private static final String VISIBLE_TO_PACKAGE_IDENTIFIER_PROPERTY = "packageName"; 77 78 /** Property that holds the SHA 256 certificate of the app that can access a schema. */ 79 private static final String VISIBLE_TO_PACKAGE_SHA_256_CERT_PROPERTY = "sha256Cert"; 80 81 /** Property that holds the required permissions to access the schema. */ 82 private static final String PERMISSION_PROPERTY = "permission"; 83 84 // The initial schema version, one VisibilityConfig contains all visibility information for 85 // whole package. 86 public static final int SCHEMA_VERSION_DOC_PER_PACKAGE = 0; 87 88 // One VisibilityConfig contains visibility information for a single schema. 89 public static final int SCHEMA_VERSION_DOC_PER_SCHEMA = 1; 90 91 // One VisibilityConfig contains visibility information for a single schema. The permission 92 // visibility information is stored in a document property VisibilityPermissionConfig of the 93 // outer doc. 94 public static final int SCHEMA_VERSION_NESTED_PERMISSION_SCHEMA = 2; 95 96 public static final int SCHEMA_VERSION_LATEST = SCHEMA_VERSION_NESTED_PERMISSION_SCHEMA; 97 98 // The initial schema version, the overlay schema contains public acl and visible to config 99 // properties. 100 public static final int OVERLAY_SCHEMA_VERSION_PUBLIC_ACL_VISIBLE_TO_CONFIG = 0; 101 102 // The overlay schema only contains a proto property contains all visibility setting. 103 public static final int OVERLAY_SCHEMA_VERSION_ALL_IN_PROTO = 1; 104 105 // The version number of schema saved in Android V overlay database. 106 public static final int ANDROID_V_OVERLAY_SCHEMA_VERSION_LATEST = 107 OVERLAY_SCHEMA_VERSION_ALL_IN_PROTO; 108 109 /** 110 * Schema for the VisibilityStore's documents. 111 * 112 * <p>NOTE: If you update this, also update {@link #SCHEMA_VERSION_LATEST}. 113 */ 114 public static final AppSearchSchema VISIBILITY_DOCUMENT_SCHEMA = 115 new AppSearchSchema.Builder(VISIBILITY_DOCUMENT_SCHEMA_TYPE) 116 .addProperty( 117 new AppSearchSchema.BooleanPropertyConfig.Builder( 118 NOT_DISPLAYED_BY_SYSTEM_PROPERTY) 119 .setCardinality( 120 AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) 121 .build()) 122 .addProperty( 123 new AppSearchSchema.StringPropertyConfig.Builder( 124 VISIBLE_TO_PACKAGE_IDENTIFIER_PROPERTY) 125 .setCardinality( 126 AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED) 127 .build()) 128 .addProperty( 129 new AppSearchSchema.BytesPropertyConfig.Builder( 130 VISIBLE_TO_PACKAGE_SHA_256_CERT_PROPERTY) 131 .setCardinality( 132 AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED) 133 .build()) 134 .addProperty( 135 new AppSearchSchema.DocumentPropertyConfig.Builder( 136 PERMISSION_PROPERTY, 137 VisibilityPermissionConfig.SCHEMA_TYPE) 138 .setCardinality( 139 AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED) 140 .build()) 141 .build(); 142 143 /** Schema for the VisibilityStore's Android V visibility setting overlay. */ 144 public static final AppSearchSchema ANDROID_V_OVERLAY_SCHEMA = 145 new AppSearchSchema.Builder(ANDROID_V_OVERLAY_SCHEMA_TYPE) 146 .addProperty( 147 new AppSearchSchema.BytesPropertyConfig.Builder( 148 VISIBILITY_PROTO_SERIALIZE_PROPERTY) 149 .setCardinality( 150 AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) 151 .build()) 152 .build(); 153 154 /** 155 * The Deprecated schemas and properties that we need to remove from visibility database. 156 * TODO(b/321326441) remove this method when we no longer to migrate devices in this state. 157 */ 158 static final AppSearchSchema DEPRECATED_PUBLIC_ACL_OVERLAY_SCHEMA = 159 new AppSearchSchema.Builder("PublicAclOverlayType") 160 .addProperty( 161 new AppSearchSchema.StringPropertyConfig.Builder( 162 "publiclyVisibleTargetPackage") 163 .setCardinality( 164 AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) 165 .build()) 166 .addProperty( 167 new AppSearchSchema.BytesPropertyConfig.Builder( 168 "publiclyVisibleTargetPackageSha256Cert") 169 .setCardinality( 170 AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) 171 .build()) 172 .build(); 173 174 /** 175 * Constructs a {@link InternalVisibilityConfig} from two {@link GenericDocument}s. 176 * 177 * <p>This constructor is still needed until we don't treat Visibility related documents as 178 * {@link GenericDocument}s internally. 179 * 180 * @param visibilityDocument a {@link GenericDocument} holding visibility properties in {@link 181 * #VISIBILITY_DOCUMENT_SCHEMA} 182 * @param androidVOverlayDocument a {@link GenericDocument} holding visibility properties in 183 * {@link #ANDROID_V_OVERLAY_SCHEMA} 184 */ 185 @NonNull createInternalVisibilityConfig( @onNull GenericDocument visibilityDocument, @Nullable GenericDocument androidVOverlayDocument)186 public static InternalVisibilityConfig createInternalVisibilityConfig( 187 @NonNull GenericDocument visibilityDocument, 188 @Nullable GenericDocument androidVOverlayDocument) { 189 Objects.requireNonNull(visibilityDocument); 190 191 // Parse visibility proto if required 192 AndroidVOverlayProto androidVOverlayProto = null; 193 if (androidVOverlayDocument != null) { 194 try { 195 byte[] androidVOverlayProtoBytes = 196 androidVOverlayDocument.getPropertyBytes( 197 VISIBILITY_PROTO_SERIALIZE_PROPERTY); 198 if (androidVOverlayProtoBytes != null) { 199 androidVOverlayProto = 200 AndroidVOverlayProto.parseFrom(androidVOverlayProtoBytes); 201 } 202 } catch (InvalidProtocolBufferException e) { 203 Log.e(TAG, "Get an invalid android V visibility overlay proto.", e); 204 } 205 } 206 207 // Handle all visibility settings other than visibleToConfigs 208 SchemaVisibilityConfig schemaVisibilityConfig = 209 createVisibilityConfig(visibilityDocument, androidVOverlayProto); 210 211 // Handle visibleToConfigs 212 String schemaType = visibilityDocument.getId(); 213 InternalVisibilityConfig.Builder builder = 214 new InternalVisibilityConfig.Builder(schemaType) 215 .setNotDisplayedBySystem( 216 visibilityDocument.getPropertyBoolean( 217 NOT_DISPLAYED_BY_SYSTEM_PROPERTY)) 218 .setVisibilityConfig(schemaVisibilityConfig); 219 if (androidVOverlayProto != null) { 220 List<VisibilityConfigProto> visibleToConfigProtoList = 221 androidVOverlayProto.getVisibleToConfigsList(); 222 for (int i = 0; i < visibleToConfigProtoList.size(); i++) { 223 SchemaVisibilityConfig visibleToConfig = 224 convertVisibilityConfigFromProto(visibleToConfigProtoList.get(i)); 225 builder.addVisibleToConfig(visibleToConfig); 226 } 227 } 228 229 return builder.build(); 230 } 231 232 /** 233 * Constructs a {@link SchemaVisibilityConfig} from a {@link GenericDocument} containing legacy 234 * visibility settings, and an {@link AndroidVOverlayProto} containing extended visibility 235 * settings. 236 * 237 * <p>This constructor is still needed until we don't treat Visibility related documents as 238 * {@link GenericDocument}s internally. 239 * 240 * @param visibilityDocument a {@link GenericDocument} holding all visibility properties other 241 * than publiclyVisibleTargetPackage. 242 * @param androidVOverlayProto the proto containing post-V visibility settings 243 */ 244 @NonNull createVisibilityConfig( @onNull GenericDocument visibilityDocument, @Nullable AndroidVOverlayProto androidVOverlayProto)245 private static SchemaVisibilityConfig createVisibilityConfig( 246 @NonNull GenericDocument visibilityDocument, 247 @Nullable AndroidVOverlayProto androidVOverlayProto) { 248 Objects.requireNonNull(visibilityDocument); 249 250 // Pre-V visibility settings come from visibilityDocument 251 SchemaVisibilityConfig.Builder builder = new SchemaVisibilityConfig.Builder(); 252 253 String[] visibleToPackageNames = 254 visibilityDocument.getPropertyStringArray(VISIBLE_TO_PACKAGE_IDENTIFIER_PROPERTY); 255 byte[][] visibleToPackageShaCerts = 256 visibilityDocument.getPropertyBytesArray(VISIBLE_TO_PACKAGE_SHA_256_CERT_PROPERTY); 257 if (visibleToPackageNames != null && visibleToPackageShaCerts != null) { 258 for (int i = 0; i < visibleToPackageNames.length; i++) { 259 builder.addAllowedPackage( 260 new PackageIdentifier( 261 visibleToPackageNames[i], visibleToPackageShaCerts[i])); 262 } 263 } 264 265 GenericDocument[] visibleToPermissionDocs = 266 visibilityDocument.getPropertyDocumentArray(PERMISSION_PROPERTY); 267 if (visibleToPermissionDocs != null) { 268 for (int i = 0; i < visibleToPermissionDocs.length; ++i) { 269 long[] visibleToPermissionLongs = 270 visibleToPermissionDocs[i].getPropertyLongArray( 271 VisibilityPermissionConfig.ALL_REQUIRED_PERMISSIONS_PROPERTY); 272 if (visibleToPermissionLongs != null) { 273 Set<Integer> allRequiredPermissions = 274 new ArraySet<>(visibleToPermissionLongs.length); 275 for (int j = 0; j < visibleToPermissionLongs.length; j++) { 276 allRequiredPermissions.add((int) visibleToPermissionLongs[j]); 277 } 278 builder.addRequiredPermissions(allRequiredPermissions); 279 } 280 } 281 } 282 283 // Post-V visibility settings come from androidVOverlayProto 284 if (androidVOverlayProto != null) { 285 SchemaVisibilityConfig androidVOverlayConfig = 286 convertVisibilityConfigFromProto(androidVOverlayProto.getVisibilityConfig()); 287 builder.setPubliclyVisibleTargetPackage( 288 androidVOverlayConfig.getPubliclyVisibleTargetPackage()); 289 } 290 291 return builder.build(); 292 } 293 294 @NonNull convertVisibilityConfigFromProto( @onNull VisibilityConfigProto proto)295 private static SchemaVisibilityConfig convertVisibilityConfigFromProto( 296 @NonNull VisibilityConfigProto proto) { 297 SchemaVisibilityConfig.Builder builder = new SchemaVisibilityConfig.Builder(); 298 299 List<PackageIdentifierProto> visibleToPackageProtoList = proto.getVisibleToPackagesList(); 300 for (int i = 0; i < visibleToPackageProtoList.size(); i++) { 301 PackageIdentifierProto visibleToPackage = proto.getVisibleToPackages(i); 302 builder.addAllowedPackage(convertPackageIdentifierFromProto(visibleToPackage)); 303 } 304 305 List<VisibleToPermissionProto> visibleToPermissionProtoList = 306 proto.getVisibleToPermissionsList(); 307 for (int i = 0; i < visibleToPermissionProtoList.size(); i++) { 308 VisibleToPermissionProto visibleToPermissionProto = visibleToPermissionProtoList.get(i); 309 Set<Integer> visibleToPermissions = 310 new ArraySet<>(visibleToPermissionProto.getPermissionsList()); 311 builder.addRequiredPermissions(visibleToPermissions); 312 } 313 314 if (proto.hasPubliclyVisibleTargetPackage()) { 315 PackageIdentifierProto publiclyVisibleTargetPackage = 316 proto.getPubliclyVisibleTargetPackage(); 317 builder.setPubliclyVisibleTargetPackage( 318 convertPackageIdentifierFromProto(publiclyVisibleTargetPackage)); 319 } 320 321 return builder.build(); 322 } 323 convertSchemaVisibilityConfigToProto( @onNull SchemaVisibilityConfig schemaVisibilityConfig)324 private static VisibilityConfigProto convertSchemaVisibilityConfigToProto( 325 @NonNull SchemaVisibilityConfig schemaVisibilityConfig) { 326 VisibilityConfigProto.Builder builder = VisibilityConfigProto.newBuilder(); 327 328 List<PackageIdentifier> visibleToPackages = schemaVisibilityConfig.getAllowedPackages(); 329 for (int i = 0; i < visibleToPackages.size(); i++) { 330 PackageIdentifier visibleToPackage = visibleToPackages.get(i); 331 builder.addVisibleToPackages(convertPackageIdentifierToProto(visibleToPackage)); 332 } 333 334 Set<Set<Integer>> visibleToPermissions = schemaVisibilityConfig.getRequiredPermissions(); 335 if (!visibleToPermissions.isEmpty()) { 336 for (Set<Integer> allRequiredPermissions : visibleToPermissions) { 337 builder.addVisibleToPermissions( 338 VisibleToPermissionProto.newBuilder() 339 .addAllPermissions(allRequiredPermissions)); 340 } 341 } 342 343 PackageIdentifier publicAclPackage = 344 schemaVisibilityConfig.getPubliclyVisibleTargetPackage(); 345 if (publicAclPackage != null) { 346 builder.setPubliclyVisibleTargetPackage( 347 convertPackageIdentifierToProto(publicAclPackage)); 348 } 349 350 return builder.build(); 351 } 352 353 /** 354 * Returns the {@link GenericDocument} for the visibility schema. 355 * 356 * @param config the configuration to populate into the document 357 */ 358 @NonNull createVisibilityDocument( @onNull InternalVisibilityConfig config)359 public static GenericDocument createVisibilityDocument( 360 @NonNull InternalVisibilityConfig config) { 361 GenericDocument.Builder<?> builder = 362 new GenericDocument.Builder<>( 363 VISIBILITY_DOCUMENT_NAMESPACE, 364 config.getSchemaType(), // We are using the prefixedSchemaType to be the id 365 VISIBILITY_DOCUMENT_SCHEMA_TYPE); 366 builder.setPropertyBoolean( 367 NOT_DISPLAYED_BY_SYSTEM_PROPERTY, config.isNotDisplayedBySystem()); 368 SchemaVisibilityConfig schemaVisibilityConfig = config.getVisibilityConfig(); 369 List<PackageIdentifier> visibleToPackages = schemaVisibilityConfig.getAllowedPackages(); 370 String[] visibleToPackageNames = new String[visibleToPackages.size()]; 371 byte[][] visibleToPackageSha256Certs = new byte[visibleToPackages.size()][32]; 372 for (int i = 0; i < visibleToPackages.size(); i++) { 373 visibleToPackageNames[i] = visibleToPackages.get(i).getPackageName(); 374 visibleToPackageSha256Certs[i] = visibleToPackages.get(i).getSha256Certificate(); 375 } 376 builder.setPropertyString(VISIBLE_TO_PACKAGE_IDENTIFIER_PROPERTY, visibleToPackageNames); 377 builder.setPropertyBytes( 378 VISIBLE_TO_PACKAGE_SHA_256_CERT_PROPERTY, visibleToPackageSha256Certs); 379 380 // Generate an array of GenericDocument for VisibilityPermissionConfig. 381 Set<Set<Integer>> visibleToPermissions = schemaVisibilityConfig.getRequiredPermissions(); 382 if (!visibleToPermissions.isEmpty()) { 383 GenericDocument[] permissionGenericDocs = 384 new GenericDocument[visibleToPermissions.size()]; 385 int i = 0; 386 for (Set<Integer> allRequiredPermissions : visibleToPermissions) { 387 VisibilityPermissionConfig permissionDocument = 388 new VisibilityPermissionConfig(allRequiredPermissions); 389 permissionGenericDocs[i++] = permissionDocument.toGenericDocument(); 390 } 391 builder.setPropertyDocument(PERMISSION_PROPERTY, permissionGenericDocs); 392 } 393 394 // The creationTimestamp doesn't matter for Visibility documents. 395 // But to make tests pass, we set it 0 so two GenericDocuments generated from 396 // the same VisibilityConfig can be same. 397 builder.setCreationTimestampMillis(0L); 398 399 return builder.build(); 400 } 401 402 /** 403 * Returns the {@link GenericDocument} for the Android V overlay schema if it is provided, null 404 * otherwise. 405 */ 406 @Nullable createAndroidVOverlay( @onNull InternalVisibilityConfig internalVisibilityConfig)407 public static GenericDocument createAndroidVOverlay( 408 @NonNull InternalVisibilityConfig internalVisibilityConfig) { 409 PackageIdentifier publiclyVisibleTargetPackage = 410 internalVisibilityConfig.getVisibilityConfig().getPubliclyVisibleTargetPackage(); 411 Set<SchemaVisibilityConfig> visibleToConfigs = 412 internalVisibilityConfig.getVisibleToConfigs(); 413 if (publiclyVisibleTargetPackage == null && visibleToConfigs.isEmpty()) { 414 // This config doesn't contains any Android V overlay settings 415 return null; 416 } 417 418 VisibilityConfigProto.Builder visibilityConfigProtoBuilder = 419 VisibilityConfigProto.newBuilder(); 420 // Set publicAcl 421 if (publiclyVisibleTargetPackage != null) { 422 visibilityConfigProtoBuilder.setPubliclyVisibleTargetPackage( 423 convertPackageIdentifierToProto(publiclyVisibleTargetPackage)); 424 } 425 426 // Set visibleToConfigs 427 AndroidVOverlayProto.Builder androidVOverlayProtoBuilder = 428 AndroidVOverlayProto.newBuilder().setVisibilityConfig(visibilityConfigProtoBuilder); 429 if (!visibleToConfigs.isEmpty()) { 430 for (SchemaVisibilityConfig visibleToConfig : visibleToConfigs) { 431 VisibilityConfigProto visibleToConfigProto = 432 convertSchemaVisibilityConfigToProto(visibleToConfig); 433 androidVOverlayProtoBuilder.addVisibleToConfigs(visibleToConfigProto); 434 } 435 } 436 437 GenericDocument.Builder<?> androidVOverlayBuilder = 438 new GenericDocument.Builder<>( 439 ANDROID_V_OVERLAY_NAMESPACE, 440 internalVisibilityConfig.getSchemaType(), 441 ANDROID_V_OVERLAY_SCHEMA_TYPE) 442 .setPropertyBytes( 443 VISIBILITY_PROTO_SERIALIZE_PROPERTY, 444 androidVOverlayProtoBuilder.build().toByteArray()); 445 446 // The creationTimestamp doesn't matter for Visibility documents. 447 // But to make tests pass, we set it 0 so two GenericDocuments generated from 448 // the same VisibilityConfig can be same. 449 androidVOverlayBuilder.setCreationTimestampMillis(0L); 450 451 return androidVOverlayBuilder.build(); 452 } 453 454 @NonNull convertPackageIdentifierToProto( @onNull PackageIdentifier packageIdentifier)455 private static PackageIdentifierProto convertPackageIdentifierToProto( 456 @NonNull PackageIdentifier packageIdentifier) { 457 return PackageIdentifierProto.newBuilder() 458 .setPackageName(packageIdentifier.getPackageName()) 459 .setPackageSha256Cert(ByteString.copyFrom(packageIdentifier.getSha256Certificate())) 460 .build(); 461 } 462 463 @NonNull convertPackageIdentifierFromProto( @onNull PackageIdentifierProto packageIdentifierProto)464 private static PackageIdentifier convertPackageIdentifierFromProto( 465 @NonNull PackageIdentifierProto packageIdentifierProto) { 466 return new PackageIdentifier( 467 packageIdentifierProto.getPackageName(), 468 packageIdentifierProto.getPackageSha256Cert().toByteArray()); 469 } 470 VisibilityToDocumentConverter()471 private VisibilityToDocumentConverter() {} 472 } 473