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