1 /*
2  * Copyright (C) 2020 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 android.content.integrity;
18 
19 import static com.android.internal.util.Preconditions.checkArgument;
20 
21 import android.annotation.IntDef;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.os.Parcel;
25 import android.os.Parcelable;
26 
27 import com.android.internal.annotations.VisibleForTesting;
28 
29 import java.lang.annotation.Retention;
30 import java.lang.annotation.RetentionPolicy;
31 import java.nio.charset.StandardCharsets;
32 import java.security.MessageDigest;
33 import java.security.NoSuchAlgorithmException;
34 import java.util.Collections;
35 import java.util.List;
36 import java.util.Objects;
37 
38 /**
39  * Represents a simple formula consisting of an app install metadata field and a value.
40  *
41  * <p>Instances of this class are immutable.
42  *
43  * @hide
44  */
45 @VisibleForTesting
46 public abstract class AtomicFormula extends IntegrityFormula {
47 
48     /** @hide */
49     @IntDef(
50             value = {
51                 PACKAGE_NAME,
52                 APP_CERTIFICATE,
53                 INSTALLER_NAME,
54                 INSTALLER_CERTIFICATE,
55                 VERSION_CODE,
56                 PRE_INSTALLED,
57                 STAMP_TRUSTED,
58                 STAMP_CERTIFICATE_HASH,
59                 APP_CERTIFICATE_LINEAGE,
60             })
61     @Retention(RetentionPolicy.SOURCE)
62     public @interface Key {}
63 
64     /** @hide */
65     @IntDef(value = {EQ, GT, GTE})
66     @Retention(RetentionPolicy.SOURCE)
67     public @interface Operator {}
68 
69     /**
70      * Package name of the app.
71      *
72      * <p>Can only be used in {@link StringAtomicFormula}.
73      */
74     public static final int PACKAGE_NAME = 0;
75 
76     /**
77      * SHA-256 of the app certificate of the app.
78      *
79      * <p>Can only be used in {@link StringAtomicFormula}.
80      */
81     public static final int APP_CERTIFICATE = 1;
82 
83     /**
84      * Package name of the installer. Will be empty string if installed by the system (e.g., adb).
85      *
86      * <p>Can only be used in {@link StringAtomicFormula}.
87      */
88     public static final int INSTALLER_NAME = 2;
89 
90     /**
91      * SHA-256 of the cert of the installer. Will be empty string if installed by the system (e.g.,
92      * adb).
93      *
94      * <p>Can only be used in {@link StringAtomicFormula}.
95      */
96     public static final int INSTALLER_CERTIFICATE = 3;
97 
98     /**
99      * Version code of the app.
100      *
101      * <p>Can only be used in {@link LongAtomicFormula}.
102      */
103     public static final int VERSION_CODE = 4;
104 
105     /**
106      * If the app is pre-installed on the device.
107      *
108      * <p>Can only be used in {@link BooleanAtomicFormula}.
109      */
110     public static final int PRE_INSTALLED = 5;
111 
112     /**
113      * If the APK has an embedded trusted stamp.
114      *
115      * <p>Can only be used in {@link BooleanAtomicFormula}.
116      */
117     public static final int STAMP_TRUSTED = 6;
118 
119     /**
120      * SHA-256 of the certificate used to sign the stamp embedded in the APK.
121      *
122      * <p>Can only be used in {@link StringAtomicFormula}.
123      */
124     public static final int STAMP_CERTIFICATE_HASH = 7;
125 
126     /**
127      * SHA-256 of a certificate in the signing lineage of the app.
128      *
129      * <p>Can only be used in {@link StringAtomicFormula}.
130      */
131     public static final int APP_CERTIFICATE_LINEAGE = 8;
132 
133     public static final int EQ = 0;
134     public static final int GT = 1;
135     public static final int GTE = 2;
136 
137     private final @Key int mKey;
138 
AtomicFormula(@ey int key)139     public AtomicFormula(@Key int key) {
140         checkArgument(isValidKey(key), "Unknown key: %d", key);
141         mKey = key;
142     }
143 
144     /** An {@link AtomicFormula} with an key and long value. */
145     public static final class LongAtomicFormula extends AtomicFormula implements Parcelable {
146         private final Long mValue;
147         private final @Operator Integer mOperator;
148 
149         /**
150          * Constructs an empty {@link LongAtomicFormula}. This should only be used as a base.
151          *
152          * <p>This formula will always return false.
153          *
154          * @throws IllegalArgumentException if {@code key} cannot be used with long value
155          */
LongAtomicFormula(@ey int key)156         public LongAtomicFormula(@Key int key) {
157             super(key);
158             checkArgument(
159                     key == VERSION_CODE,
160                     "Key %s cannot be used with LongAtomicFormula", keyToString(key));
161             mValue = null;
162             mOperator = null;
163         }
164 
165         /**
166          * Constructs a new {@link LongAtomicFormula}.
167          *
168          * <p>This formula will hold if and only if the corresponding information of an install
169          * specified by {@code key} is of the correct relationship to {@code value} as specified by
170          * {@code operator}.
171          *
172          * @throws IllegalArgumentException if {@code key} cannot be used with long value
173          */
LongAtomicFormula(@ey int key, @Operator int operator, long value)174         public LongAtomicFormula(@Key int key, @Operator int operator, long value) {
175             super(key);
176             checkArgument(
177                     key == VERSION_CODE,
178                     "Key %s cannot be used with LongAtomicFormula", keyToString(key));
179             checkArgument(
180                     isValidOperator(operator), "Unknown operator: %d", operator);
181             mOperator = operator;
182             mValue = value;
183         }
184 
LongAtomicFormula(Parcel in)185         LongAtomicFormula(Parcel in) {
186             super(in.readInt());
187             mValue = in.readLong();
188             mOperator = in.readInt();
189         }
190 
191         @NonNull
192         public static final Creator<LongAtomicFormula> CREATOR =
193                 new Creator<LongAtomicFormula>() {
194                     @Override
195                     public LongAtomicFormula createFromParcel(Parcel in) {
196                         return new LongAtomicFormula(in);
197                     }
198 
199                     @Override
200                     public LongAtomicFormula[] newArray(int size) {
201                         return new LongAtomicFormula[size];
202                     }
203                 };
204 
205         @Override
getTag()206         public int getTag() {
207             return IntegrityFormula.LONG_ATOMIC_FORMULA_TAG;
208         }
209 
210         @Override
matches(AppInstallMetadata appInstallMetadata)211         public boolean matches(AppInstallMetadata appInstallMetadata) {
212             if (mValue == null || mOperator == null) {
213                 return false;
214             }
215 
216             long metadataValue = getLongMetadataValue(appInstallMetadata, getKey());
217             switch (mOperator) {
218                 case EQ:
219                     return metadataValue == mValue;
220                 case GT:
221                     return metadataValue > mValue;
222                 case GTE:
223                     return metadataValue >= mValue;
224                 default:
225                     throw new IllegalArgumentException(
226                             String.format("Unexpected operator %d", mOperator));
227             }
228         }
229 
230         @Override
isAppCertificateFormula()231         public boolean isAppCertificateFormula() {
232             return false;
233         }
234 
235         @Override
isAppCertificateLineageFormula()236         public boolean isAppCertificateLineageFormula() {
237             return false;
238         }
239 
240         @Override
isInstallerFormula()241         public boolean isInstallerFormula() {
242             return false;
243         }
244 
245         @Override
toString()246         public String toString() {
247             if (mValue == null || mOperator == null) {
248                 return String.format("(%s)", keyToString(getKey()));
249             }
250             return String.format(
251                     "(%s %s %s)", keyToString(getKey()), operatorToString(mOperator), mValue);
252         }
253 
254         @Override
equals(@ullable Object o)255         public boolean equals(@Nullable Object o) {
256             if (this == o) {
257                 return true;
258             }
259             if (o == null || getClass() != o.getClass()) {
260                 return false;
261             }
262             LongAtomicFormula that = (LongAtomicFormula) o;
263             return getKey() == that.getKey()
264                     && Objects.equals(mValue, that.mValue)
265                     && Objects.equals(mOperator, that.mOperator);
266         }
267 
268         @Override
hashCode()269         public int hashCode() {
270             return Objects.hash(getKey(), mOperator, mValue);
271         }
272 
273         @Override
describeContents()274         public int describeContents() {
275             return 0;
276         }
277 
278         @Override
writeToParcel(@onNull Parcel dest, int flags)279         public void writeToParcel(@NonNull Parcel dest, int flags) {
280             if (mValue == null || mOperator == null) {
281                 throw new IllegalStateException("Cannot write an empty LongAtomicFormula.");
282             }
283             dest.writeInt(getKey());
284             dest.writeLong(mValue);
285             dest.writeInt(mOperator);
286         }
287 
getValue()288         public Long getValue() {
289             return mValue;
290         }
291 
getOperator()292         public Integer getOperator() {
293             return mOperator;
294         }
295 
isValidOperator(int operator)296         private static boolean isValidOperator(int operator) {
297             return operator == EQ || operator == GT || operator == GTE;
298         }
299 
getLongMetadataValue(AppInstallMetadata appInstallMetadata, int key)300         private static long getLongMetadataValue(AppInstallMetadata appInstallMetadata, int key) {
301             switch (key) {
302                 case AtomicFormula.VERSION_CODE:
303                     return appInstallMetadata.getVersionCode();
304                 default:
305                     throw new IllegalStateException("Unexpected key in IntAtomicFormula" + key);
306             }
307         }
308     }
309 
310     /** An {@link AtomicFormula} with a key and string value. */
311     public static final class StringAtomicFormula extends AtomicFormula implements Parcelable {
312         private final String mValue;
313         // Indicates whether the value is the actual value or the hashed value.
314         private final Boolean mIsHashedValue;
315 
316         /**
317          * Constructs an empty {@link StringAtomicFormula}. This should only be used as a base.
318          *
319          * <p>An empty formula will always match to false.
320          *
321          * @throws IllegalArgumentException if {@code key} cannot be used with string value
322          */
StringAtomicFormula(@ey int key)323         public StringAtomicFormula(@Key int key) {
324             super(key);
325             checkArgument(
326                     key == PACKAGE_NAME
327                             || key == APP_CERTIFICATE
328                             || key == INSTALLER_CERTIFICATE
329                             || key == INSTALLER_NAME
330                             || key == STAMP_CERTIFICATE_HASH
331                             || key == APP_CERTIFICATE_LINEAGE,
332                     "Key %s cannot be used with StringAtomicFormula", keyToString(key));
333             mValue = null;
334             mIsHashedValue = null;
335         }
336 
337         /**
338          * Constructs a new {@link StringAtomicFormula}.
339          *
340          * <p>This formula will hold if and only if the corresponding information of an install
341          * specified by {@code key} equals {@code value}.
342          *
343          * @throws IllegalArgumentException if {@code key} cannot be used with string value
344          */
StringAtomicFormula(@ey int key, @NonNull String value, boolean isHashed)345         public StringAtomicFormula(@Key int key, @NonNull String value, boolean isHashed) {
346             super(key);
347             checkArgument(
348                     key == PACKAGE_NAME
349                             || key == APP_CERTIFICATE
350                             || key == INSTALLER_CERTIFICATE
351                             || key == INSTALLER_NAME
352                             || key == STAMP_CERTIFICATE_HASH
353                             || key == APP_CERTIFICATE_LINEAGE,
354                     "Key %s cannot be used with StringAtomicFormula", keyToString(key));
355             mValue = value;
356             mIsHashedValue = isHashed;
357         }
358 
359         /**
360          * Constructs a new {@link StringAtomicFormula} together with handling the necessary hashing
361          * for the given key.
362          *
363          * <p>The value will be automatically hashed with SHA256 and the hex digest will be computed
364          * when the key is PACKAGE_NAME or INSTALLER_NAME and the value is more than 32 characters.
365          *
366          * <p>The APP_CERTIFICATES, INSTALLER_CERTIFICATES, STAMP_CERTIFICATE_HASH and
367          * APP_CERTIFICATE_LINEAGE are always delivered in hashed form. So the isHashedValue is set
368          * to true by default.
369          *
370          * @throws IllegalArgumentException if {@code key} cannot be used with string value.
371          */
StringAtomicFormula(@ey int key, @NonNull String value)372         public StringAtomicFormula(@Key int key, @NonNull String value) {
373             super(key);
374             checkArgument(
375                     key == PACKAGE_NAME
376                             || key == APP_CERTIFICATE
377                             || key == INSTALLER_CERTIFICATE
378                             || key == INSTALLER_NAME
379                             || key == STAMP_CERTIFICATE_HASH
380                             || key == APP_CERTIFICATE_LINEAGE,
381                     "Key %s cannot be used with StringAtomicFormula", keyToString(key));
382             mValue = hashValue(key, value);
383             mIsHashedValue =
384                     (key == APP_CERTIFICATE
385                                     || key == INSTALLER_CERTIFICATE
386                                     || key == STAMP_CERTIFICATE_HASH
387                                     || key == APP_CERTIFICATE_LINEAGE)
388                             || !mValue.equals(value);
389         }
390 
StringAtomicFormula(Parcel in)391         StringAtomicFormula(Parcel in) {
392             super(in.readInt());
393             mValue = in.readStringNoHelper();
394             mIsHashedValue = in.readByte() != 0;
395         }
396 
397         @NonNull
398         public static final Creator<StringAtomicFormula> CREATOR =
399                 new Creator<StringAtomicFormula>() {
400                     @Override
401                     public StringAtomicFormula createFromParcel(Parcel in) {
402                         return new StringAtomicFormula(in);
403                     }
404 
405                     @Override
406                     public StringAtomicFormula[] newArray(int size) {
407                         return new StringAtomicFormula[size];
408                     }
409                 };
410 
411         @Override
getTag()412         public int getTag() {
413             return IntegrityFormula.STRING_ATOMIC_FORMULA_TAG;
414         }
415 
416         @Override
matches(AppInstallMetadata appInstallMetadata)417         public boolean matches(AppInstallMetadata appInstallMetadata) {
418             if (mValue == null || mIsHashedValue == null) {
419                 return false;
420             }
421             return getMetadataValue(appInstallMetadata, getKey()).contains(mValue);
422         }
423 
424         @Override
isAppCertificateFormula()425         public boolean isAppCertificateFormula() {
426             return getKey() == APP_CERTIFICATE;
427         }
428 
429         @Override
isAppCertificateLineageFormula()430         public boolean isAppCertificateLineageFormula() {
431             return getKey() == APP_CERTIFICATE_LINEAGE;
432         }
433 
434         @Override
isInstallerFormula()435         public boolean isInstallerFormula() {
436             return getKey() == INSTALLER_NAME || getKey() == INSTALLER_CERTIFICATE;
437         }
438 
439         @Override
toString()440         public String toString() {
441             if (mValue == null || mIsHashedValue == null) {
442                 return String.format("(%s)", keyToString(getKey()));
443             }
444             return String.format("(%s %s %s)", keyToString(getKey()), operatorToString(EQ), mValue);
445         }
446 
447         @Override
equals(@ullable Object o)448         public boolean equals(@Nullable Object o) {
449             if (this == o) {
450                 return true;
451             }
452             if (o == null || getClass() != o.getClass()) {
453                 return false;
454             }
455             StringAtomicFormula that = (StringAtomicFormula) o;
456             return getKey() == that.getKey() && Objects.equals(mValue, that.mValue);
457         }
458 
459         @Override
hashCode()460         public int hashCode() {
461             return Objects.hash(getKey(), mValue);
462         }
463 
464         @Override
describeContents()465         public int describeContents() {
466             return 0;
467         }
468 
469         @Override
writeToParcel(@onNull Parcel dest, int flags)470         public void writeToParcel(@NonNull Parcel dest, int flags) {
471             if (mValue == null || mIsHashedValue == null) {
472                 throw new IllegalStateException("Cannot write an empty StringAtomicFormula.");
473             }
474             dest.writeInt(getKey());
475             dest.writeStringNoHelper(mValue);
476             dest.writeByte((byte) (mIsHashedValue ? 1 : 0));
477         }
478 
getValue()479         public String getValue() {
480             return mValue;
481         }
482 
getIsHashedValue()483         public Boolean getIsHashedValue() {
484             return mIsHashedValue;
485         }
486 
getMetadataValue( AppInstallMetadata appInstallMetadata, int key)487         private static List<String> getMetadataValue(
488                 AppInstallMetadata appInstallMetadata, int key) {
489             switch (key) {
490                 case AtomicFormula.PACKAGE_NAME:
491                     return Collections.singletonList(appInstallMetadata.getPackageName());
492                 case AtomicFormula.APP_CERTIFICATE:
493                     return appInstallMetadata.getAppCertificates();
494                 case AtomicFormula.INSTALLER_CERTIFICATE:
495                     return appInstallMetadata.getInstallerCertificates();
496                 case AtomicFormula.INSTALLER_NAME:
497                     return Collections.singletonList(appInstallMetadata.getInstallerName());
498                 case AtomicFormula.STAMP_CERTIFICATE_HASH:
499                     return Collections.singletonList(appInstallMetadata.getStampCertificateHash());
500                 case AtomicFormula.APP_CERTIFICATE_LINEAGE:
501                     return appInstallMetadata.getAppCertificateLineage();
502                 default:
503                     throw new IllegalStateException(
504                             "Unexpected key in StringAtomicFormula: " + key);
505             }
506         }
507 
hashValue(@ey int key, String value)508         private static String hashValue(@Key int key, String value) {
509             // Hash the string value if it is a PACKAGE_NAME or INSTALLER_NAME and the value is
510             // greater than 32 characters.
511             if (value.length() > 32) {
512                 if (key == PACKAGE_NAME || key == INSTALLER_NAME) {
513                     return hash(value);
514                 }
515             }
516             return value;
517         }
518 
hash(String value)519         private static String hash(String value) {
520             try {
521                 MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
522                 byte[] hashBytes = messageDigest.digest(value.getBytes(StandardCharsets.UTF_8));
523                 return IntegrityUtils.getHexDigest(hashBytes);
524             } catch (NoSuchAlgorithmException e) {
525                 throw new RuntimeException("SHA-256 algorithm not found", e);
526             }
527         }
528     }
529 
530     /** An {@link AtomicFormula} with a key and boolean value. */
531     public static final class BooleanAtomicFormula extends AtomicFormula implements Parcelable {
532         private final Boolean mValue;
533 
534         /**
535          * Constructs an empty {@link BooleanAtomicFormula}. This should only be used as a base.
536          *
537          * <p>An empty formula will always match to false.
538          *
539          * @throws IllegalArgumentException if {@code key} cannot be used with boolean value
540          */
BooleanAtomicFormula(@ey int key)541         public BooleanAtomicFormula(@Key int key) {
542             super(key);
543             checkArgument(
544                     key == PRE_INSTALLED || key == STAMP_TRUSTED,
545                     String.format(
546                             "Key %s cannot be used with BooleanAtomicFormula", keyToString(key)));
547             mValue = null;
548         }
549 
550         /**
551          * Constructs a new {@link BooleanAtomicFormula}.
552          *
553          * <p>This formula will hold if and only if the corresponding information of an install
554          * specified by {@code key} equals {@code value}.
555          *
556          * @throws IllegalArgumentException if {@code key} cannot be used with boolean value
557          */
BooleanAtomicFormula(@ey int key, boolean value)558         public BooleanAtomicFormula(@Key int key, boolean value) {
559             super(key);
560             checkArgument(
561                     key == PRE_INSTALLED || key == STAMP_TRUSTED,
562                     String.format(
563                             "Key %s cannot be used with BooleanAtomicFormula", keyToString(key)));
564             mValue = value;
565         }
566 
BooleanAtomicFormula(Parcel in)567         BooleanAtomicFormula(Parcel in) {
568             super(in.readInt());
569             mValue = in.readByte() != 0;
570         }
571 
572         @NonNull
573         public static final Creator<BooleanAtomicFormula> CREATOR =
574                 new Creator<BooleanAtomicFormula>() {
575                     @Override
576                     public BooleanAtomicFormula createFromParcel(Parcel in) {
577                         return new BooleanAtomicFormula(in);
578                     }
579 
580                     @Override
581                     public BooleanAtomicFormula[] newArray(int size) {
582                         return new BooleanAtomicFormula[size];
583                     }
584                 };
585 
586         @Override
getTag()587         public int getTag() {
588             return IntegrityFormula.BOOLEAN_ATOMIC_FORMULA_TAG;
589         }
590 
591         @Override
matches(AppInstallMetadata appInstallMetadata)592         public boolean matches(AppInstallMetadata appInstallMetadata) {
593             if (mValue == null) {
594                 return false;
595             }
596             return getBooleanMetadataValue(appInstallMetadata, getKey()) == mValue;
597         }
598 
599         @Override
isAppCertificateFormula()600         public boolean isAppCertificateFormula() {
601             return false;
602         }
603 
604         @Override
isAppCertificateLineageFormula()605         public boolean isAppCertificateLineageFormula() {
606             return false;
607         }
608 
609         @Override
isInstallerFormula()610         public boolean isInstallerFormula() {
611             return false;
612         }
613 
614         @Override
toString()615         public String toString() {
616             if (mValue == null) {
617                 return String.format("(%s)", keyToString(getKey()));
618             }
619             return String.format("(%s %s %s)", keyToString(getKey()), operatorToString(EQ), mValue);
620         }
621 
622         @Override
equals(@ullable Object o)623         public boolean equals(@Nullable Object o) {
624             if (this == o) {
625                 return true;
626             }
627             if (o == null || getClass() != o.getClass()) {
628                 return false;
629             }
630             BooleanAtomicFormula that = (BooleanAtomicFormula) o;
631             return getKey() == that.getKey() && Objects.equals(mValue, that.mValue);
632         }
633 
634         @Override
hashCode()635         public int hashCode() {
636             return Objects.hash(getKey(), mValue);
637         }
638 
639         @Override
describeContents()640         public int describeContents() {
641             return 0;
642         }
643 
644         @Override
writeToParcel(@onNull Parcel dest, int flags)645         public void writeToParcel(@NonNull Parcel dest, int flags) {
646             if (mValue == null) {
647                 throw new IllegalStateException("Cannot write an empty BooleanAtomicFormula.");
648             }
649             dest.writeInt(getKey());
650             dest.writeByte((byte) (mValue ? 1 : 0));
651         }
652 
getValue()653         public Boolean getValue() {
654             return mValue;
655         }
656 
getBooleanMetadataValue( AppInstallMetadata appInstallMetadata, int key)657         private static boolean getBooleanMetadataValue(
658                 AppInstallMetadata appInstallMetadata, int key) {
659             switch (key) {
660                 case AtomicFormula.PRE_INSTALLED:
661                     return appInstallMetadata.isPreInstalled();
662                 case AtomicFormula.STAMP_TRUSTED:
663                     return appInstallMetadata.isStampTrusted();
664                 default:
665                     throw new IllegalStateException(
666                             "Unexpected key in BooleanAtomicFormula: " + key);
667             }
668         }
669     }
670 
getKey()671     public int getKey() {
672         return mKey;
673     }
674 
keyToString(int key)675     static String keyToString(int key) {
676         switch (key) {
677             case PACKAGE_NAME:
678                 return "PACKAGE_NAME";
679             case APP_CERTIFICATE:
680                 return "APP_CERTIFICATE";
681             case VERSION_CODE:
682                 return "VERSION_CODE";
683             case INSTALLER_NAME:
684                 return "INSTALLER_NAME";
685             case INSTALLER_CERTIFICATE:
686                 return "INSTALLER_CERTIFICATE";
687             case PRE_INSTALLED:
688                 return "PRE_INSTALLED";
689             case STAMP_TRUSTED:
690                 return "STAMP_TRUSTED";
691             case STAMP_CERTIFICATE_HASH:
692                 return "STAMP_CERTIFICATE_HASH";
693             case APP_CERTIFICATE_LINEAGE:
694                 return "APP_CERTIFICATE_LINEAGE";
695             default:
696                 throw new IllegalArgumentException("Unknown key " + key);
697         }
698     }
699 
operatorToString(int op)700     static String operatorToString(int op) {
701         switch (op) {
702             case EQ:
703                 return "EQ";
704             case GT:
705                 return "GT";
706             case GTE:
707                 return "GTE";
708             default:
709                 throw new IllegalArgumentException("Unknown operator " + op);
710         }
711     }
712 
isValidKey(int key)713     private static boolean isValidKey(int key) {
714         return key == PACKAGE_NAME
715                 || key == APP_CERTIFICATE
716                 || key == VERSION_CODE
717                 || key == INSTALLER_NAME
718                 || key == INSTALLER_CERTIFICATE
719                 || key == PRE_INSTALLED
720                 || key == STAMP_TRUSTED
721                 || key == STAMP_CERTIFICATE_HASH
722                 || key == APP_CERTIFICATE_LINEAGE;
723     }
724 }
725