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