Merge "Rollforward: Digest API: allow installer to provide digests for an APK."

This commit is contained in:
Alex Buynytskyy
2020-09-14 18:28:00 +00:00
committed by Android (Google) Code Review
15 changed files with 838 additions and 272 deletions

View File

@@ -11447,6 +11447,16 @@ package android.content.pm {
field public final float widthFraction;
}
public final class ApkChecksum implements android.os.Parcelable {
method public int describeContents();
method public int getKind();
method @Nullable public java.security.cert.Certificate getSourceCertificate() throws java.security.cert.CertificateException;
method @Nullable public String getSplitName();
method @NonNull public byte[] getValue();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.ApkChecksum> CREATOR;
}
public class ApplicationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
ctor public ApplicationInfo();
ctor public ApplicationInfo(android.content.pm.ApplicationInfo);
@@ -11548,6 +11558,21 @@ package android.content.pm {
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.ChangedPackages> CREATOR;
}
public final class Checksum implements android.os.Parcelable {
method public int describeContents();
method public int getKind();
method @NonNull public byte[] getValue();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.Checksum> CREATOR;
field public static final int PARTIAL_MERKLE_ROOT_1M_SHA256 = 32; // 0x20
field public static final int PARTIAL_MERKLE_ROOT_1M_SHA512 = 64; // 0x40
field public static final int WHOLE_MD5 = 2; // 0x2
field public static final int WHOLE_MERKLE_ROOT_4K_SHA256 = 1; // 0x1
field public static final int WHOLE_SHA1 = 4; // 0x4
field public static final int WHOLE_SHA256 = 8; // 0x8
field public static final int WHOLE_SHA512 = 16; // 0x10
}
public class ComponentInfo extends android.content.pm.PackageItemInfo {
ctor public ComponentInfo();
ctor public ComponentInfo(android.content.pm.ComponentInfo);
@@ -11619,16 +11644,6 @@ package android.content.pm {
field public int version;
}
public final class FileChecksum implements android.os.Parcelable {
method public int describeContents();
method public int getKind();
method @Nullable public java.security.cert.Certificate getSourceCertificate() throws java.security.cert.CertificateException;
method @Nullable public String getSplitName();
method @NonNull public byte[] getValue();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.FileChecksum> CREATOR;
}
public final class InstallSourceInfo implements android.os.Parcelable {
method public int describeContents();
method @Nullable public String getInitiatingPackageName();
@@ -11849,6 +11864,7 @@ package android.content.pm {
public static class PackageInstaller.Session implements java.io.Closeable {
method public void abandon();
method public void addChecksums(@NonNull String, @NonNull java.util.List<android.content.pm.Checksum>) throws java.io.IOException;
method public void addChildSessionId(int);
method public void close();
method public void commit(@NonNull android.content.IntentSender);
@@ -12250,8 +12266,6 @@ package android.content.pm {
field public static final int MATCH_SYSTEM_ONLY = 1048576; // 0x100000
field public static final int MATCH_UNINSTALLED_PACKAGES = 8192; // 0x2000
field public static final long MAXIMUM_VERIFICATION_TIMEOUT = 3600000L; // 0x36ee80L
field public static final int PARTIAL_MERKLE_ROOT_1M_SHA256 = 32; // 0x20
field public static final int PARTIAL_MERKLE_ROOT_1M_SHA512 = 64; // 0x40
field public static final int PERMISSION_DENIED = -1; // 0xffffffff
field public static final int PERMISSION_GRANTED = 0; // 0x0
field public static final int SIGNATURE_FIRST_NOT_SIGNED = -1; // 0xffffffff
@@ -12266,11 +12280,6 @@ package android.content.pm {
field public static final int VERIFICATION_ALLOW = 1; // 0x1
field public static final int VERIFICATION_REJECT = -1; // 0xffffffff
field public static final int VERSION_CODE_HIGHEST = -1; // 0xffffffff
field public static final int WHOLE_MD5 = 2; // 0x2
field public static final int WHOLE_MERKLE_ROOT_4K_SHA256 = 1; // 0x1
field public static final int WHOLE_SHA1 = 4; // 0x4
field public static final int WHOLE_SHA256 = 8; // 0x8
field public static final int WHOLE_SHA512 = 16; // 0x10
}
public static class PackageManager.NameNotFoundException extends android.util.AndroidException {

View File

@@ -16,6 +16,14 @@
package android.app;
import static android.content.pm.Checksum.PARTIAL_MERKLE_ROOT_1M_SHA256;
import static android.content.pm.Checksum.PARTIAL_MERKLE_ROOT_1M_SHA512;
import static android.content.pm.Checksum.WHOLE_MD5;
import static android.content.pm.Checksum.WHOLE_MERKLE_ROOT_4K_SHA256;
import static android.content.pm.Checksum.WHOLE_SHA1;
import static android.content.pm.Checksum.WHOLE_SHA256;
import static android.content.pm.Checksum.WHOLE_SHA512;
import android.annotation.DrawableRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -32,6 +40,7 @@ import android.content.IntentSender;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ChangedPackages;
import android.content.pm.Checksum;
import android.content.pm.ComponentInfo;
import android.content.pm.FeatureInfo;
import android.content.pm.IPackageDataObserver;
@@ -973,7 +982,7 @@ public class ApplicationPackageManager extends PackageManager {
@Override
public void getChecksums(@NonNull String packageName, boolean includeSplits,
@FileChecksumKind int required, @Nullable List<Certificate> trustedInstallers,
@Checksum.Kind int required, @Nullable List<Certificate> trustedInstallers,
@NonNull IntentSender statusReceiver)
throws CertificateEncodingException, IOException, NameNotFoundException {
Objects.requireNonNull(packageName);

View File

@@ -16,5 +16,5 @@
package android.content.pm;
parcelable FileChecksum;
parcelable ApkChecksum;

View File

@@ -34,51 +34,71 @@ import java.security.cert.X509Certificate;
import java.util.List;
/**
* A typed checksum.
* A typed checksum of an APK.
*
* @see PackageManager#getChecksums(String, boolean, int, List, IntentSender)
*/
@DataClass(genHiddenConstructor = true)
public final class FileChecksum implements Parcelable {
@DataClass.Suppress({"getChecksum"})
public final class ApkChecksum implements Parcelable {
/**
* Checksum for which split. Null indicates base.apk.
*/
private final @Nullable String mSplitName;
/**
* Checksum kind.
* Checksum.
*/
private final @PackageManager.FileChecksumKind int mKind;
/**
* Checksum value.
*/
private final @NonNull byte[] mValue;
private final @NonNull Checksum mChecksum;
/**
* For Installer-provided checksums, certificate of the Installer/AppStore.
*/
private final @Nullable byte[] mSourceCertificate;
/**
* Constructor, internal use only
* Constructor, internal use only.
*
* @hide
*/
public FileChecksum(@Nullable String splitName, @PackageManager.FileChecksumKind int kind,
public ApkChecksum(@Nullable String splitName, @Checksum.Kind int kind,
@NonNull byte[] value) {
this(splitName, kind, value, (byte[]) null);
this(splitName, new Checksum(kind, value), (byte[]) null);
}
/**
* Constructor, internal use only
* Constructor, internal use only.
*
* @hide
*/
public FileChecksum(@Nullable String splitName, @PackageManager.FileChecksumKind int kind,
public ApkChecksum(@Nullable String splitName, @Checksum.Kind int kind,
@NonNull byte[] value, @Nullable Certificate sourceCertificate)
throws CertificateEncodingException {
this(splitName, kind, value,
this(splitName, new Checksum(kind, value),
(sourceCertificate != null) ? sourceCertificate.getEncoded() : null);
}
/**
* Checksum kind.
*/
public @Checksum.Kind int getKind() {
return mChecksum.getKind();
}
/**
* Checksum value.
*/
public @NonNull byte[] getValue() {
return mChecksum.getValue();
}
/**
* Returns raw bytes representing encoded certificate of the source of this checksum.
* @hide
*/
public @Nullable byte[] getSourceCertificateBytes() {
return mSourceCertificate;
}
/**
* Certificate of the source of this checksum.
* @throws CertificateException in case when certificate can't be re-created from serialized
@@ -102,7 +122,7 @@ public final class FileChecksum implements Parcelable {
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/FileChecksum.java
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/ApkChecksum.java
//
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
// Settings > Editor > Code Style > Formatter Control
@@ -110,31 +130,25 @@ public final class FileChecksum implements Parcelable {
/**
* Creates a new FileChecksum.
* Creates a new ApkChecksum.
*
* @param splitName
* Checksum for which split. Null indicates base.apk.
* @param kind
* Checksum kind.
* @param value
* Checksum value.
* @param checksum
* Checksum.
* @param sourceCertificate
* For Installer-provided checksums, certificate of the Installer/AppStore.
* @hide
*/
@DataClass.Generated.Member
public FileChecksum(
public ApkChecksum(
@Nullable String splitName,
@PackageManager.FileChecksumKind int kind,
@NonNull byte[] value,
@NonNull Checksum checksum,
@Nullable byte[] sourceCertificate) {
this.mSplitName = splitName;
this.mKind = kind;
this.mChecksum = checksum;
com.android.internal.util.AnnotationValidations.validate(
PackageManager.FileChecksumKind.class, null, mKind);
this.mValue = value;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mValue);
NonNull.class, null, mChecksum);
this.mSourceCertificate = sourceCertificate;
// onConstructed(); // You can define this method to get a callback
@@ -148,22 +162,6 @@ public final class FileChecksum implements Parcelable {
return mSplitName;
}
/**
* Checksum kind.
*/
@DataClass.Generated.Member
public @PackageManager.FileChecksumKind int getKind() {
return mKind;
}
/**
* Checksum value.
*/
@DataClass.Generated.Member
public @NonNull byte[] getValue() {
return mValue;
}
@Override
@DataClass.Generated.Member
public void writeToParcel(@NonNull Parcel dest, int flags) {
@@ -172,11 +170,10 @@ public final class FileChecksum implements Parcelable {
byte flg = 0;
if (mSplitName != null) flg |= 0x1;
if (mSourceCertificate != null) flg |= 0x8;
if (mSourceCertificate != null) flg |= 0x4;
dest.writeByte(flg);
if (mSplitName != null) dest.writeString(mSplitName);
dest.writeInt(mKind);
dest.writeByteArray(mValue);
dest.writeTypedObject(mChecksum, flags);
if (mSourceCertificate != null) dest.writeByteArray(mSourceCertificate);
}
@@ -187,47 +184,43 @@ public final class FileChecksum implements Parcelable {
/** @hide */
@SuppressWarnings({"unchecked", "RedundantCast"})
@DataClass.Generated.Member
/* package-private */ FileChecksum(@NonNull Parcel in) {
/* package-private */ ApkChecksum(@NonNull Parcel in) {
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
byte flg = in.readByte();
String splitName = (flg & 0x1) == 0 ? null : in.readString();
int kind = in.readInt();
byte[] value = in.createByteArray();
byte[] sourceCertificate = (flg & 0x8) == 0 ? null : in.createByteArray();
Checksum checksum = (Checksum) in.readTypedObject(Checksum.CREATOR);
byte[] sourceCertificate = (flg & 0x4) == 0 ? null : in.createByteArray();
this.mSplitName = splitName;
this.mKind = kind;
this.mChecksum = checksum;
com.android.internal.util.AnnotationValidations.validate(
PackageManager.FileChecksumKind.class, null, mKind);
this.mValue = value;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mValue);
NonNull.class, null, mChecksum);
this.mSourceCertificate = sourceCertificate;
// onConstructed(); // You can define this method to get a callback
}
@DataClass.Generated.Member
public static final @NonNull Parcelable.Creator<FileChecksum> CREATOR
= new Parcelable.Creator<FileChecksum>() {
public static final @NonNull Parcelable.Creator<ApkChecksum> CREATOR
= new Parcelable.Creator<ApkChecksum>() {
@Override
public FileChecksum[] newArray(int size) {
return new FileChecksum[size];
public ApkChecksum[] newArray(int size) {
return new ApkChecksum[size];
}
@Override
public FileChecksum createFromParcel(@NonNull Parcel in) {
return new FileChecksum(in);
public ApkChecksum createFromParcel(@NonNull Parcel in) {
return new ApkChecksum(in);
}
};
@DataClass.Generated(
time = 1598322801861L,
time = 1599845645160L,
codegenVersion = "1.0.15",
sourceFile = "frameworks/base/core/java/android/content/pm/FileChecksum.java",
inputSignatures = "private final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.content.pm.PackageManager.FileChecksumKind int mKind\nprivate final @android.annotation.NonNull byte[] mValue\nprivate final @android.annotation.Nullable byte[] mSourceCertificate\npublic @android.annotation.Nullable java.security.cert.Certificate getSourceCertificate()\nclass FileChecksum extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstructor=true)")
sourceFile = "frameworks/base/core/java/android/content/pm/ApkChecksum.java",
inputSignatures = "private final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.NonNull android.content.pm.Checksum mChecksum\nprivate final @android.annotation.Nullable byte[] mSourceCertificate\npublic @android.content.pm.Checksum.Kind int getKind()\npublic @android.annotation.NonNull byte[] getValue()\npublic @android.annotation.Nullable byte[] getSourceCertificateBytes()\npublic @android.annotation.Nullable java.security.cert.Certificate getSourceCertificate()\nclass ApkChecksum extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstructor=true)")
@Deprecated
private void __metadata() {}

View File

@@ -0,0 +1,20 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.content.pm;
parcelable Checksum;

View File

@@ -0,0 +1,228 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.content.pm;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
import com.android.internal.util.DataClass;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
/**
* A typed checksum.
*
* @see PackageInstaller.Session#addChecksums(String, List)
*/
@DataClass(genHiddenConstructor = true, genConstDefs = false)
public final class Checksum implements Parcelable {
/**
* Root SHA256 hash of a 4K Merkle tree computed over all file bytes.
* <a href="https://source.android.com/security/apksigning/v4">See APK Signature Scheme V4</a>.
* <a href="https://git.kernel.org/pub/scm/fs/fscrypt/fscrypt.git/tree/Documentation/filesystems/fsverity.rst">See fs-verity</a>.
*
* @see PackageManager#getChecksums
*/
public static final int WHOLE_MERKLE_ROOT_4K_SHA256 = 0x00000001;
/**
* MD5 hash computed over all file bytes.
*
* @see PackageManager#getChecksums
*/
public static final int WHOLE_MD5 = 0x00000002;
/**
* SHA1 hash computed over all file bytes.
*
* @see PackageManager#getChecksums
*/
public static final int WHOLE_SHA1 = 0x00000004;
/**
* SHA256 hash computed over all file bytes.
*
* @see PackageManager#getChecksums
*/
public static final int WHOLE_SHA256 = 0x00000008;
/**
* SHA512 hash computed over all file bytes.
*
* @see PackageManager#getChecksums
*/
public static final int WHOLE_SHA512 = 0x00000010;
/**
* Root SHA256 hash of a 1M Merkle tree computed over protected content.
* Excludes signing block.
* <a href="https://source.android.com/security/apksigning/v2">See APK Signature Scheme V2</a>.
*
* @see PackageManager#getChecksums
*/
public static final int PARTIAL_MERKLE_ROOT_1M_SHA256 = 0x00000020;
/**
* Root SHA512 hash of a 1M Merkle tree computed over protected content.
* Excludes signing block.
* <a href="https://source.android.com/security/apksigning/v2">See APK Signature Scheme V2</a>.
*
* @see PackageManager#getChecksums
*/
public static final int PARTIAL_MERKLE_ROOT_1M_SHA512 = 0x00000040;
/** @hide */
@IntDef(flag = true, prefix = {"WHOLE_", "PARTIAL_"}, value = {
WHOLE_MERKLE_ROOT_4K_SHA256,
WHOLE_MD5,
WHOLE_SHA1,
WHOLE_SHA256,
WHOLE_SHA512,
PARTIAL_MERKLE_ROOT_1M_SHA256,
PARTIAL_MERKLE_ROOT_1M_SHA512,
})
@Retention(RetentionPolicy.SOURCE)
public @interface Kind {}
/**
* Checksum kind.
*/
private final @Checksum.Kind int mKind;
/**
* Checksum value.
*/
private final @NonNull byte[] mValue;
// Code below generated by codegen v1.0.15.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/Checksum.java
//
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
// Settings > Editor > Code Style > Formatter Control
//@formatter:off
/**
* Creates a new Checksum.
*
* @param kind
* Checksum kind.
* @param value
* Checksum value.
* @hide
*/
@DataClass.Generated.Member
public Checksum(
@Checksum.Kind int kind,
@NonNull byte[] value) {
this.mKind = kind;
com.android.internal.util.AnnotationValidations.validate(
Checksum.Kind.class, null, mKind);
this.mValue = value;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mValue);
// onConstructed(); // You can define this method to get a callback
}
/**
* Checksum kind.
*/
@DataClass.Generated.Member
public @Checksum.Kind int getKind() {
return mKind;
}
/**
* Checksum value.
*/
@DataClass.Generated.Member
public @NonNull byte[] getValue() {
return mValue;
}
@Override
@DataClass.Generated.Member
public void writeToParcel(@NonNull Parcel dest, int flags) {
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
dest.writeInt(mKind);
dest.writeByteArray(mValue);
}
@Override
@DataClass.Generated.Member
public int describeContents() { return 0; }
/** @hide */
@SuppressWarnings({"unchecked", "RedundantCast"})
@DataClass.Generated.Member
/* package-private */ Checksum(@NonNull Parcel in) {
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
int kind = in.readInt();
byte[] value = in.createByteArray();
this.mKind = kind;
com.android.internal.util.AnnotationValidations.validate(
Checksum.Kind.class, null, mKind);
this.mValue = value;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mValue);
// onConstructed(); // You can define this method to get a callback
}
@DataClass.Generated.Member
public static final @NonNull Parcelable.Creator<Checksum> CREATOR
= new Parcelable.Creator<Checksum>() {
@Override
public Checksum[] newArray(int size) {
return new Checksum[size];
}
@Override
public Checksum createFromParcel(@NonNull Parcel in) {
return new Checksum(in);
}
};
@DataClass.Generated(
time = 1599845646883L,
codegenVersion = "1.0.15",
sourceFile = "frameworks/base/core/java/android/content/pm/Checksum.java",
inputSignatures = "public static final int WHOLE_MERKLE_ROOT_4K_SHA256\npublic static final int WHOLE_MD5\npublic static final int WHOLE_SHA1\npublic static final int WHOLE_SHA256\npublic static final int WHOLE_SHA512\npublic static final int PARTIAL_MERKLE_ROOT_1M_SHA256\npublic static final int PARTIAL_MERKLE_ROOT_1M_SHA512\nprivate final @android.content.pm.Checksum.Kind int mKind\nprivate final @android.annotation.NonNull byte[] mValue\nclass Checksum extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstructor=true, genConstDefs=false)")
@Deprecated
private void __metadata() {}
//@formatter:on
// End of generated code
}

View File

@@ -16,6 +16,7 @@
package android.content.pm;
import android.content.pm.Checksum;
import android.content.pm.DataLoaderParamsParcel;
import android.content.pm.IPackageInstallObserver2;
import android.content.IntentSender;
@@ -33,6 +34,8 @@ interface IPackageInstallerSession {
void write(String name, long offsetBytes, long lengthBytes, in ParcelFileDescriptor fd);
void addChecksums(String name, in Checksum[] checksums);
void removeSplit(String splitName);
void close();

View File

@@ -1218,6 +1218,31 @@ public class PackageInstaller {
}
}
/**
* Adds installer-provided checksums for the APK file in session.
*
* @param name previously written as part of this session.
* @param checksums installer intends to make available via
* {@link PackageManager#getChecksums(String, boolean, int, List,
* IntentSender)}.
* @throws SecurityException if called after the session has been
* committed or abandoned.
*/
public void addChecksums(@NonNull String name, @NonNull List<Checksum> checksums)
throws IOException {
Objects.requireNonNull(name);
Objects.requireNonNull(checksums);
try {
mSession.addChecksums(name, checksums.toArray(new Checksum[checksums.size()]));
} catch (RuntimeException e) {
ExceptionUtils.maybeUnwrapIOException(e);
throw e;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Attempt to commit everything staged in this session. This may require
* user intervention, and so it may not happen immediately. The final

View File

@@ -7843,74 +7843,6 @@ public abstract class PackageManager {
"hasSigningCertificate not implemented in subclass");
}
/**
* Root SHA256 hash of a 4K Merkle tree computed over all file bytes.
* <a href="https://source.android.com/security/apksigning/v4">See APK Signature Scheme V4</a>.
* <a href="https://git.kernel.org/pub/scm/fs/fscrypt/fscrypt.git/tree/Documentation/filesystems/fsverity.rst">See fs-verity</a>.
*
* @see #getChecksums
*/
public static final int WHOLE_MERKLE_ROOT_4K_SHA256 = 0x00000001;
/**
* MD5 hash computed over all file bytes.
*
* @see #getChecksums
*/
public static final int WHOLE_MD5 = 0x00000002;
/**
* SHA1 hash computed over all file bytes.
*
* @see #getChecksums
*/
public static final int WHOLE_SHA1 = 0x00000004;
/**
* SHA256 hash computed over all file bytes.
*
* @see #getChecksums
*/
public static final int WHOLE_SHA256 = 0x00000008;
/**
* SHA512 hash computed over all file bytes.
*
* @see #getChecksums
*/
public static final int WHOLE_SHA512 = 0x00000010;
/**
* Root SHA256 hash of a 1M Merkle tree computed over protected content.
* Excludes signing block.
* <a href="https://source.android.com/security/apksigning/v2">See APK Signature Scheme V2</a>.
*
* @see #getChecksums
*/
public static final int PARTIAL_MERKLE_ROOT_1M_SHA256 = 0x00000020;
/**
* Root SHA512 hash of a 1M Merkle tree computed over protected content.
* Excludes signing block.
* <a href="https://source.android.com/security/apksigning/v2">See APK Signature Scheme V2</a>.
*
* @see #getChecksums
*/
public static final int PARTIAL_MERKLE_ROOT_1M_SHA512 = 0x00000040;
/** @hide */
@IntDef(flag = true, prefix = {"WHOLE_", "PARTIAL_"}, value = {
WHOLE_MERKLE_ROOT_4K_SHA256,
WHOLE_MD5,
WHOLE_SHA1,
WHOLE_SHA256,
WHOLE_SHA512,
PARTIAL_MERKLE_ROOT_1M_SHA256,
PARTIAL_MERKLE_ROOT_1M_SHA512,
})
@Retention(RetentionPolicy.SOURCE)
public @interface FileChecksumKind {}
/**
* Trust any Installer to provide checksums for the package.
* @see #getChecksums
@@ -7940,12 +7872,12 @@ public abstract class PackageManager {
* {@link #TRUST_ALL} will return checksums from any Installer,
* {@link #TRUST_NONE} disables optimized Installer-enforced checksums.
* @param statusReceiver called once when the results are available as
* {@link #EXTRA_CHECKSUMS} of type FileChecksum[].
* {@link #EXTRA_CHECKSUMS} of type ApkChecksum[].
* @throws CertificateEncodingException if an encoding error occurs for trustedInstallers.
* @throws NameNotFoundException if a package with the given name cannot be found on the system.
*/
public void getChecksums(@NonNull String packageName, boolean includeSplits,
@FileChecksumKind int required, @Nullable List<Certificate> trustedInstallers,
@Checksum.Kind int required, @Nullable List<Certificate> trustedInstallers,
@NonNull IntentSender statusReceiver)
throws CertificateEncodingException, IOException, NameNotFoundException {
throw new UnsupportedOperationException("getChecksums not implemented in subclass");

View File

@@ -11447,6 +11447,16 @@ package android.content.pm {
field public final float widthFraction;
}
public final class ApkChecksum implements android.os.Parcelable {
method public int describeContents();
method public int getKind();
method @Nullable public java.security.cert.Certificate getSourceCertificate() throws java.security.cert.CertificateException;
method @Nullable public String getSplitName();
method @NonNull public byte[] getValue();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.ApkChecksum> CREATOR;
}
public class ApplicationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
ctor public ApplicationInfo();
ctor public ApplicationInfo(android.content.pm.ApplicationInfo);
@@ -11548,6 +11558,21 @@ package android.content.pm {
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.ChangedPackages> CREATOR;
}
public final class Checksum implements android.os.Parcelable {
method public int describeContents();
method public int getKind();
method @NonNull public byte[] getValue();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.Checksum> CREATOR;
field public static final int PARTIAL_MERKLE_ROOT_1M_SHA256 = 32; // 0x20
field public static final int PARTIAL_MERKLE_ROOT_1M_SHA512 = 64; // 0x40
field public static final int WHOLE_MD5 = 2; // 0x2
field public static final int WHOLE_MERKLE_ROOT_4K_SHA256 = 1; // 0x1
field public static final int WHOLE_SHA1 = 4; // 0x4
field public static final int WHOLE_SHA256 = 8; // 0x8
field public static final int WHOLE_SHA512 = 16; // 0x10
}
public class ComponentInfo extends android.content.pm.PackageItemInfo {
ctor public ComponentInfo();
ctor public ComponentInfo(android.content.pm.ComponentInfo);
@@ -11619,16 +11644,6 @@ package android.content.pm {
field public int version;
}
public final class FileChecksum implements android.os.Parcelable {
method public int describeContents();
method public int getKind();
method @Nullable public java.security.cert.Certificate getSourceCertificate() throws java.security.cert.CertificateException;
method @Nullable public String getSplitName();
method @NonNull public byte[] getValue();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.FileChecksum> CREATOR;
}
public final class InstallSourceInfo implements android.os.Parcelable {
method public int describeContents();
method @Nullable public String getInitiatingPackageName();
@@ -11849,6 +11864,7 @@ package android.content.pm {
public static class PackageInstaller.Session implements java.io.Closeable {
method public void abandon();
method public void addChecksums(@NonNull String, @NonNull java.util.List<android.content.pm.Checksum>) throws java.io.IOException;
method public void addChildSessionId(int);
method public void close();
method public void commit(@NonNull android.content.IntentSender);
@@ -12250,8 +12266,6 @@ package android.content.pm {
field public static final int MATCH_SYSTEM_ONLY = 1048576; // 0x100000
field public static final int MATCH_UNINSTALLED_PACKAGES = 8192; // 0x2000
field public static final long MAXIMUM_VERIFICATION_TIMEOUT = 3600000L; // 0x36ee80L
field public static final int PARTIAL_MERKLE_ROOT_1M_SHA256 = 32; // 0x20
field public static final int PARTIAL_MERKLE_ROOT_1M_SHA512 = 64; // 0x40
field public static final int PERMISSION_DENIED = -1; // 0xffffffff
field public static final int PERMISSION_GRANTED = 0; // 0x0
field public static final int SIGNATURE_FIRST_NOT_SIGNED = -1; // 0xffffffff
@@ -12266,11 +12280,6 @@ package android.content.pm {
field public static final int VERIFICATION_ALLOW = 1; // 0x1
field public static final int VERIFICATION_REJECT = -1; // 0xffffffff
field public static final int VERSION_CODE_HIGHEST = -1; // 0xffffffff
field public static final int WHOLE_MD5 = 2; // 0x2
field public static final int WHOLE_MERKLE_ROOT_4K_SHA256 = 1; // 0x1
field public static final int WHOLE_SHA1 = 4; // 0x4
field public static final int WHOLE_SHA256 = 8; // 0x8
field public static final int WHOLE_SHA512 = 16; // 0x10
}
public static class PackageManager.NameNotFoundException extends android.util.AndroidException {

View File

@@ -16,14 +16,15 @@
package com.android.server.pm;
import static android.content.pm.Checksum.PARTIAL_MERKLE_ROOT_1M_SHA256;
import static android.content.pm.Checksum.PARTIAL_MERKLE_ROOT_1M_SHA512;
import static android.content.pm.Checksum.WHOLE_MD5;
import static android.content.pm.Checksum.WHOLE_MERKLE_ROOT_4K_SHA256;
import static android.content.pm.Checksum.WHOLE_SHA1;
import static android.content.pm.Checksum.WHOLE_SHA256;
import static android.content.pm.Checksum.WHOLE_SHA512;
import static android.content.pm.PackageManager.EXTRA_CHECKSUMS;
import static android.content.pm.PackageManager.PARTIAL_MERKLE_ROOT_1M_SHA256;
import static android.content.pm.PackageManager.PARTIAL_MERKLE_ROOT_1M_SHA512;
import static android.content.pm.PackageManager.WHOLE_MD5;
import static android.content.pm.PackageManager.WHOLE_MERKLE_ROOT_4K_SHA256;
import static android.content.pm.PackageManager.WHOLE_SHA1;
import static android.content.pm.PackageManager.WHOLE_SHA256;
import static android.content.pm.PackageManager.WHOLE_SHA512;
import static android.content.pm.PackageParser.APK_FILE_EXTENSION;
import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256;
import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512;
import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256;
@@ -33,14 +34,16 @@ import android.annotation.Nullable;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.FileChecksum;
import android.content.pm.PackageManager;
import android.content.pm.ApkChecksum;
import android.content.pm.Checksum;
import android.content.pm.PackageParser;
import android.content.pm.Signature;
import android.os.Handler;
import android.os.SystemClock;
import android.os.incremental.IncrementalManager;
import android.os.incremental.IncrementalStorage;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Pair;
import android.util.Slog;
import android.util.apk.ApkSignatureSchemeV2Verifier;
@@ -57,18 +60,26 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.server.security.VerityUtils;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.security.DigestException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Provides checksums for APK.
@@ -76,6 +87,8 @@ import java.util.Map;
public class ApkChecksums {
static final String TAG = "ApkChecksums";
private static final String DIGESTS_FILE_EXTENSION = ".digests";
// MessageDigest algorithms.
static final String ALGO_MD5 = "MD5";
static final String ALGO_SHA1 = "SHA1";
@@ -130,6 +143,100 @@ public class ApkChecksums {
}
}
/**
* Return the digests path associated with the given code path
* (replaces '.apk' extension with '.digests')
*
* @throws IllegalArgumentException if the code path is not an .apk.
*/
public static String buildDigestsPathForApk(String codePath) {
if (!PackageParser.isApkPath(codePath)) {
throw new IllegalStateException("Code path is not an apk " + codePath);
}
return codePath.substring(0, codePath.length() - APK_FILE_EXTENSION.length())
+ DIGESTS_FILE_EXTENSION;
}
/**
* Search for the digests file associated with the given target file.
* If it exists, the method returns the digests file; otherwise it returns null.
*/
public static File findDigestsForFile(File targetFile) {
String digestsPath = buildDigestsPathForApk(targetFile.getAbsolutePath());
File digestsFile = new File(digestsPath);
return digestsFile.exists() ? digestsFile : null;
}
/**
* Serialize checksums to file in binary format.
*/
public static void writeChecksums(File file, ApkChecksum[] checksums)
throws IOException, CertificateException {
try (OutputStream os = new FileOutputStream(file);
DataOutputStream dos = new DataOutputStream(os)) {
dos.writeInt(checksums.length);
for (ApkChecksum checksum : checksums) {
final String splitName = checksum.getSplitName();
if (splitName == null) {
dos.writeInt(-1);
} else {
dos.writeInt(splitName.length());
dos.writeUTF(splitName);
}
dos.writeInt(checksum.getKind());
final byte[] valueBytes = checksum.getValue();
dos.writeInt(valueBytes.length);
dos.write(valueBytes);
final Certificate cert = checksum.getSourceCertificate();
final byte[] certBytes = (cert == null) ? null : cert.getEncoded();
if (certBytes == null) {
dos.writeInt(-1);
} else {
dos.writeInt(certBytes.length);
dos.write(certBytes);
}
}
}
}
/**
* Deserialize array of checksums previously stored in
* {@link #writeChecksums(File, ApkChecksum[])}.
*/
private static ApkChecksum[] readChecksums(File file) throws IOException {
try (InputStream is = new FileInputStream(file);
DataInputStream dis = new DataInputStream(is)) {
final int size = dis.readInt();
ApkChecksum[] checksums = new ApkChecksum[size];
for (int i = 0; i < size; ++i) {
final String splitName;
if (dis.readInt() < 0) {
splitName = null;
} else {
splitName = dis.readUTF();
}
final int kind = dis.readInt();
final byte[] valueBytes = new byte[dis.readInt()];
dis.read(valueBytes);
final byte[] certBytes;
final int certBytesLength = dis.readInt();
if (certBytesLength < 0) {
certBytes = null;
} else {
certBytes = new byte[certBytesLength];
dis.read(certBytes);
}
checksums[i] = new ApkChecksum(splitName, new Checksum(kind, valueBytes),
certBytes);
}
return checksums;
}
}
/**
* Fetch or calculate checksums for the collection of files.
*
@@ -142,20 +249,20 @@ public class ApkChecksums {
* @param statusReceiver to receive the resulting checksums
*/
public static void getChecksums(List<Pair<String, File>> filesToChecksum,
@PackageManager.FileChecksumKind int optional,
@PackageManager.FileChecksumKind int required,
@Checksum.Kind int optional,
@Checksum.Kind int required,
@Nullable Certificate[] trustedInstallers,
@NonNull IntentSender statusReceiver,
@NonNull Injector injector) {
List<Map<Integer, FileChecksum>> result = new ArrayList<>(filesToChecksum.size());
List<Map<Integer, ApkChecksum>> result = new ArrayList<>(filesToChecksum.size());
for (int i = 0, size = filesToChecksum.size(); i < size; ++i) {
final String split = filesToChecksum.get(i).first;
final File file = filesToChecksum.get(i).second;
Map<Integer, FileChecksum> checksums = new ArrayMap<>();
Map<Integer, ApkChecksum> checksums = new ArrayMap<>();
result.add(checksums);
try {
getAvailableFileChecksums(split, file, optional | required, trustedInstallers,
getAvailableApkChecksums(split, file, optional | required, trustedInstallers,
checksums);
} catch (Throwable e) {
Slog.e(TAG, "Preferred checksum calculation error", e);
@@ -168,18 +275,18 @@ public class ApkChecksums {
}
private static void processRequiredChecksums(List<Pair<String, File>> filesToChecksum,
List<Map<Integer, FileChecksum>> result,
@PackageManager.FileChecksumKind int required,
List<Map<Integer, ApkChecksum>> result,
@Checksum.Kind int required,
@NonNull IntentSender statusReceiver,
@NonNull Injector injector,
long startTime) {
final boolean timeout =
SystemClock.uptimeMillis() - startTime >= PROCESS_REQUIRED_CHECKSUMS_TIMEOUT_MILLIS;
List<FileChecksum> allChecksums = new ArrayList<>();
List<ApkChecksum> allChecksums = new ArrayList<>();
for (int i = 0, size = filesToChecksum.size(); i < size; ++i) {
final String split = filesToChecksum.get(i).first;
final File file = filesToChecksum.get(i).second;
Map<Integer, FileChecksum> checksums = result.get(i);
Map<Integer, ApkChecksum> checksums = result.get(i);
try {
if (!timeout || required != 0) {
@@ -192,7 +299,7 @@ public class ApkChecksums {
return;
}
getRequiredFileChecksums(split, file, required, checksums);
getRequiredApkChecksums(split, file, required, checksums);
}
allChecksums.addAll(checksums.values());
} catch (Throwable e) {
@@ -202,7 +309,7 @@ public class ApkChecksums {
final Intent intent = new Intent();
intent.putExtra(EXTRA_CHECKSUMS,
allChecksums.toArray(new FileChecksum[allChecksums.size()]));
allChecksums.toArray(new ApkChecksum[allChecksums.size()]));
try {
statusReceiver.sendIntent(injector.getContext(), 1, intent, null, null);
@@ -222,16 +329,16 @@ public class ApkChecksums {
* [] - trust nobody.
* @param checksums resulting checksums
*/
private static void getAvailableFileChecksums(String split, File file,
@PackageManager.FileChecksumKind int kinds,
private static void getAvailableApkChecksums(String split, File file,
@Checksum.Kind int kinds,
@Nullable Certificate[] trustedInstallers,
Map<Integer, FileChecksum> checksums) {
Map<Integer, ApkChecksum> checksums) {
final String filePath = file.getAbsolutePath();
// Always available: FSI or IncFs.
if (isRequired(WHOLE_MERKLE_ROOT_4K_SHA256, kinds, checksums)) {
// Hashes in fs-verity and IncFS are always verified.
FileChecksum checksum = extractHashFromFS(split, filePath);
ApkChecksum checksum = extractHashFromFS(split, filePath);
if (checksum != null) {
checksums.put(checksum.getKind(), checksum);
}
@@ -240,22 +347,40 @@ public class ApkChecksums {
// System enforced: v2/v3.
if (isRequired(PARTIAL_MERKLE_ROOT_1M_SHA256, kinds, checksums) || isRequired(
PARTIAL_MERKLE_ROOT_1M_SHA512, kinds, checksums)) {
Map<Integer, FileChecksum> v2v3checksums = extractHashFromV2V3Signature(
Map<Integer, ApkChecksum> v2v3checksums = extractHashFromV2V3Signature(
split, filePath, kinds);
if (v2v3checksums != null) {
checksums.putAll(v2v3checksums);
}
}
// TODO(b/160605420): Installer provided.
if (trustedInstallers == null || trustedInstallers.length > 0) {
final File digestsFile = new File(buildDigestsPathForApk(filePath));
if (digestsFile.exists()) {
try {
final ApkChecksum[] digests = readChecksums(digestsFile);
final Set<Signature> trusted = convertToSet(trustedInstallers);
for (ApkChecksum digest : digests) {
if (isRequired(digest.getKind(), kinds, checksums) && isTrusted(digest,
trusted)) {
checksums.put(digest.getKind(), digest);
}
}
} catch (IOException e) {
Slog.e(TAG, "Error reading .digests", e);
} catch (CertificateEncodingException e) {
Slog.e(TAG, "Error encoding trustedInstallers", e);
}
}
}
}
/**
* Whether the file is available for checksumming or we need to wait.
*/
private static boolean needToWait(File file,
@PackageManager.FileChecksumKind int kinds,
Map<Integer, FileChecksum> checksums,
@Checksum.Kind int kinds,
Map<Integer, ApkChecksum> checksums,
@NonNull Injector injector) throws IOException {
if (!isRequired(WHOLE_MERKLE_ROOT_4K_SHA256, kinds, checksums)
&& !isRequired(WHOLE_MD5, kinds, checksums)
@@ -274,12 +399,13 @@ public class ApkChecksums {
IncrementalManager manager = injector.getIncrementalManager();
if (manager == null) {
throw new IllegalStateException("IncrementalManager is missing.");
Slog.e(TAG, "IncrementalManager is missing.");
return false;
}
IncrementalStorage storage = manager.openStorage(filePath);
if (storage == null) {
throw new IllegalStateException(
"IncrementalStorage is missing for a path on IncFs: " + filePath);
Slog.e(TAG, "IncrementalStorage is missing for a path on IncFs: " + filePath);
return false;
}
return !storage.isFileFullyLoaded(filePath);
@@ -293,9 +419,9 @@ public class ApkChecksums {
* @param kinds mask to forcefully calculate if not available
* @param checksums resulting checksums
*/
private static void getRequiredFileChecksums(String split, File file,
@PackageManager.FileChecksumKind int kinds,
Map<Integer, FileChecksum> checksums) {
private static void getRequiredApkChecksums(String split, File file,
@Checksum.Kind int kinds,
Map<Integer, ApkChecksum> checksums) {
final String filePath = file.getAbsolutePath();
// Manually calculating required checksums if not readily available.
@@ -310,7 +436,7 @@ public class ApkChecksums {
}
});
checksums.put(WHOLE_MERKLE_ROOT_4K_SHA256,
new FileChecksum(split, WHOLE_MERKLE_ROOT_4K_SHA256, generatedRootHash));
new ApkChecksum(split, WHOLE_MERKLE_ROOT_4K_SHA256, generatedRootHash));
} catch (IOException | NoSuchAlgorithmException | DigestException e) {
Slog.e(TAG, "Error calculating WHOLE_MERKLE_ROOT_4K_SHA256", e);
}
@@ -324,8 +450,8 @@ public class ApkChecksums {
calculatePartialChecksumsIfRequested(checksums, split, file, kinds);
}
private static boolean isRequired(@PackageManager.FileChecksumKind int kind,
@PackageManager.FileChecksumKind int kinds, Map<Integer, FileChecksum> checksums) {
private static boolean isRequired(@Checksum.Kind int kind,
@Checksum.Kind int kinds, Map<Integer, ApkChecksum> checksums) {
if ((kinds & kind) == 0) {
return false;
}
@@ -335,12 +461,36 @@ public class ApkChecksums {
return true;
}
private static FileChecksum extractHashFromFS(String split, String filePath) {
/**
* Signature class provides a fast way to compare certificates using their hashes.
* The hash is exactly the same as in X509/Certificate.
*/
private static Set<Signature> convertToSet(@Nullable Certificate[] array) throws
CertificateEncodingException {
if (array == null) {
return null;
}
final Set<Signature> set = new ArraySet<>(array.length);
for (Certificate item : array) {
set.add(new Signature(item.getEncoded()));
}
return set;
}
private static boolean isTrusted(ApkChecksum checksum, Set<Signature> trusted) {
if (trusted == null) {
return true;
}
final Signature signature = new Signature(checksum.getSourceCertificateBytes());
return trusted.contains(signature);
}
private static ApkChecksum extractHashFromFS(String split, String filePath) {
// verity first
{
byte[] hash = VerityUtils.getFsverityRootHash(filePath);
if (hash != null) {
return new FileChecksum(split, WHOLE_MERKLE_ROOT_4K_SHA256, hash);
return new ApkChecksum(split, WHOLE_MERKLE_ROOT_4K_SHA256, hash);
}
}
// v4 next
@@ -350,7 +500,7 @@ public class ApkChecksums {
byte[] hash = signer.contentDigests.getOrDefault(CONTENT_DIGEST_VERITY_CHUNKED_SHA256,
null);
if (hash != null) {
return new FileChecksum(split, WHOLE_MERKLE_ROOT_4K_SHA256, hash);
return new ApkChecksum(split, WHOLE_MERKLE_ROOT_4K_SHA256, hash);
}
} catch (SignatureNotFoundException e) {
// Nothing
@@ -360,7 +510,7 @@ public class ApkChecksums {
return null;
}
private static Map<Integer, FileChecksum> extractHashFromV2V3Signature(
private static Map<Integer, ApkChecksum> extractHashFromV2V3Signature(
String split, String filePath, int kinds) {
Map<Integer, byte[]> contentDigests = null;
try {
@@ -377,19 +527,19 @@ public class ApkChecksums {
return null;
}
Map<Integer, FileChecksum> checksums = new ArrayMap<>();
Map<Integer, ApkChecksum> checksums = new ArrayMap<>();
if ((kinds & PARTIAL_MERKLE_ROOT_1M_SHA256) != 0) {
byte[] hash = contentDigests.getOrDefault(CONTENT_DIGEST_CHUNKED_SHA256, null);
if (hash != null) {
checksums.put(PARTIAL_MERKLE_ROOT_1M_SHA256,
new FileChecksum(split, PARTIAL_MERKLE_ROOT_1M_SHA256, hash));
new ApkChecksum(split, PARTIAL_MERKLE_ROOT_1M_SHA256, hash));
}
}
if ((kinds & PARTIAL_MERKLE_ROOT_1M_SHA512) != 0) {
byte[] hash = contentDigests.getOrDefault(CONTENT_DIGEST_CHUNKED_SHA512, null);
if (hash != null) {
checksums.put(PARTIAL_MERKLE_ROOT_1M_SHA512,
new FileChecksum(split, PARTIAL_MERKLE_ROOT_1M_SHA512, hash));
new ApkChecksum(split, PARTIAL_MERKLE_ROOT_1M_SHA512, hash));
}
}
return checksums;
@@ -411,17 +561,17 @@ public class ApkChecksums {
}
}
private static void calculateChecksumIfRequested(Map<Integer, FileChecksum> checksums,
private static void calculateChecksumIfRequested(Map<Integer, ApkChecksum> checksums,
String split, File file, int required, int kind) {
if ((required & kind) != 0 && !checksums.containsKey(kind)) {
final byte[] checksum = getFileChecksum(file, kind);
final byte[] checksum = getApkChecksum(file, kind);
if (checksum != null) {
checksums.put(kind, new FileChecksum(split, kind, checksum));
checksums.put(kind, new ApkChecksum(split, kind, checksum));
}
}
}
private static byte[] getFileChecksum(File file, int kind) {
private static byte[] getApkChecksum(File file, int kind) {
try (FileInputStream fis = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(fis)) {
byte[] dataBytes = new byte[512 * 1024];
@@ -466,7 +616,7 @@ public class ApkChecksums {
}
}
private static void calculatePartialChecksumsIfRequested(Map<Integer, FileChecksum> checksums,
private static void calculatePartialChecksumsIfRequested(Map<Integer, ApkChecksum> checksums,
String split, File file, int required) {
boolean needSignatureSha256 =
(required & PARTIAL_MERKLE_ROOT_1M_SHA256) != 0 && !checksums.containsKey(
@@ -500,7 +650,7 @@ public class ApkChecksums {
for (int i = 0, size = digestAlgos.length; i < size; ++i) {
int checksumKind = getChecksumKindForContentDigestAlgo(digestAlgos[i]);
if (checksumKind != -1) {
checksums.put(checksumKind, new FileChecksum(split, checksumKind, digests[i]));
checksums.put(checksumKind, new ApkChecksum(split, checksumKind, digests[i]));
}
}
} catch (IOException | DigestException e) {

View File

@@ -732,9 +732,9 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
installerAttributionTag);
session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this,
mInstallThread.getLooper(), mStagingManager, sessionId, userId, callingUid,
installSource, params, createdMillis,
stageDir, stageCid, null, false, false, false, false, null, SessionInfo.INVALID_ID,
false, false, false, SessionInfo.STAGED_SESSION_NO_ERROR, "");
installSource, params, createdMillis, stageDir, stageCid, null, null, false, false,
false, false, null, SessionInfo.INVALID_ID, false, false, false,
SessionInfo.STAGED_SESSION_NO_ERROR, "");
synchronized (mSessions) {
mSessions.put(sessionId, session);

View File

@@ -27,6 +27,7 @@ import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
import static android.content.pm.PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE;
import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SPLIT;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING;
import static android.content.pm.PackageParser.APEX_FILE_EXTENSION;
import static android.content.pm.PackageParser.APK_FILE_EXTENSION;
import static android.system.OsConstants.O_CREAT;
@@ -61,7 +62,9 @@ import android.content.IIntentReceiver;
import android.content.IIntentSender;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.ApkChecksum;
import android.content.pm.ApplicationInfo;
import android.content.pm.Checksum;
import android.content.pm.DataLoaderManager;
import android.content.pm.DataLoaderParams;
import android.content.pm.DataLoaderParamsParcel;
@@ -84,6 +87,7 @@ import android.content.pm.PackageParser;
import android.content.pm.PackageParser.ApkLite;
import android.content.pm.PackageParser.PackageLite;
import android.content.pm.PackageParser.PackageParserException;
import android.content.pm.Signature;
import android.content.pm.dex.DexMetadataHelper;
import android.content.pm.parsing.ApkLiteParseUtils;
import android.content.pm.parsing.result.ParseResult;
@@ -118,6 +122,7 @@ import android.system.Os;
import android.system.OsConstants;
import android.system.StructStat;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.ExceptionUtils;
import android.util.MathUtils;
@@ -153,6 +158,7 @@ import java.io.FileDescriptor;
import java.io.FileFilter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -176,6 +182,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
static final String TAG_SESSION = "session";
static final String TAG_CHILD_SESSION = "childSession";
static final String TAG_SESSION_FILE = "sessionFile";
static final String TAG_SESSION_CHECKSUM = "sessionChecksum";
private static final String TAG_GRANTED_RUNTIME_PERMISSION = "granted-runtime-permission";
private static final String TAG_WHITELISTED_RESTRICTED_PERMISSION =
"whitelisted-restricted-permission";
@@ -230,10 +237,14 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
private static final String ATTR_LENGTH_BYTES = "lengthBytes";
private static final String ATTR_METADATA = "metadata";
private static final String ATTR_SIGNATURE = "signature";
private static final String ATTR_CHECKSUM_KIND = "checksumKind";
private static final String ATTR_CHECKSUM_VALUE = "checksumValue";
private static final String ATTR_CHECKSUM_CERTIFICATE = "checksumCertificate";
private static final String PROPERTY_NAME_INHERIT_NATIVE = "pi.inherit_native_on_dont_kill";
private static final int[] EMPTY_CHILD_SESSION_ARRAY = EmptyArray.INT;
private static final InstallationFile[] EMPTY_INSTALLATION_FILE_ARRAY = {};
private static final ApkChecksum[] EMPTY_FILE_CHECKSUM_ARRAY = {};
private static final String SYSTEM_DATA_LOADER_PACKAGE = "android";
@@ -380,6 +391,27 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
@GuardedBy("mLock")
private ArraySet<FileEntry> mFiles = new ArraySet<>();
static class CertifiedChecksum {
final @NonNull Checksum mChecksum;
final @NonNull byte[] mCertificate;
CertifiedChecksum(@NonNull Checksum checksum, @NonNull byte[] certificate) {
mChecksum = checksum;
mCertificate = certificate;
}
Checksum getChecksum() {
return mChecksum;
}
byte[] getCertificate() {
return mCertificate;
}
}
@GuardedBy("mLock")
private ArrayMap<String, List<CertifiedChecksum>> mChecksums = new ArrayMap<>();
@GuardedBy("mLock")
private boolean mStagedSessionApplied;
@GuardedBy("mLock")
@@ -581,8 +613,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
PackageSessionProvider sessionProvider, Looper looper, StagingManager stagingManager,
int sessionId, int userId, int installerUid, @NonNull InstallSource installSource,
SessionParams params, long createdMillis,
File stageDir, String stageCid, InstallationFile[] files, boolean prepared,
boolean committed, boolean destroyed, boolean sealed,
File stageDir, String stageCid, InstallationFile[] files,
ArrayMap<String, List<CertifiedChecksum>> checksums,
boolean prepared, boolean committed, boolean destroyed, boolean sealed,
@Nullable int[] childSessionIds, int parentSessionId, boolean isReady,
boolean isFailed, boolean isApplied, int stagedSessionErrorCode,
String stagedSessionErrorMessage) {
@@ -615,6 +648,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
this.mParentSessionId = parentSessionId;
if (files != null) {
mFiles.ensureCapacity(files.length);
for (int i = 0, size = files.length; i < size; ++i) {
InstallationFile file = files[i];
if (!mFiles.add(new FileEntry(i, file))) {
@@ -624,6 +658,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
}
if (checksums != null) {
mChecksums.putAll(checksums);
}
if (!params.isMultiPackage && (stageDir == null) == (stageCid == null)) {
throw new IllegalArgumentException(
"Exactly one of stageDir or stageCid stage must be set");
@@ -906,6 +944,45 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
return filterFiles(stageDir, names, sRemovedFilter);
}
@Override
public void addChecksums(String name, @NonNull Checksum[] checksums) {
if (checksums.length == 0) {
return;
}
final PackageManagerInternal pmi = LocalServices.getService(
PackageManagerInternal.class);
final AndroidPackage callingInstaller = pmi.getPackage(Binder.getCallingUid());
if (callingInstaller == null) {
throw new IllegalStateException("Can't obtain calling installer's package.");
}
// Obtaining array of certificates used for signing the installer package.
// According to V2/V3 signing schema, the first certificate corresponds to public key
// in the signing block.
Signature[] certs = callingInstaller.getSigningDetails().signatures;
if (certs == null || certs.length == 0 || certs[0] == null) {
throw new IllegalStateException(
"Can't obtain calling installer package's certificates.");
}
byte[] mainCertificateBytes = certs[0].toByteArray();
synchronized (mLock) {
assertCallerIsOwnerOrRootLocked();
assertPreparedAndNotCommittedOrDestroyedLocked("addChecksums");
for (Checksum checksum : checksums) {
List<CertifiedChecksum> fileChecksums = mChecksums.get(name);
if (fileChecksums == null) {
fileChecksums = new ArrayList<>();
mChecksums.put(name, fileChecksums);
}
fileChecksums.add(new CertifiedChecksum(checksum, mainCertificateBytes));
}
}
}
@Override
public void removeSplit(String splitName) {
if (isDataLoaderInstallation()) {
@@ -2155,7 +2232,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
final File targetFile = new File(stageDir, targetName);
resolveAndStageFileLocked(addedFile, targetFile);
resolveAndStageFileLocked(addedFile, targetFile, null);
mResolvedBaseFile = targetFile;
// Populate package name of the apex session
@@ -2174,6 +2251,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
}
private static String splitNameToFileName(String splitName) {
if (splitName == null) {
return "base";
}
return "split_" + splitName;
}
/**
* Validate install by confirming that all application packages are have
* consistent package name, version code, and signing certificates.
@@ -2259,37 +2343,21 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
assertApkConsistentLocked(String.valueOf(addedFile), apk);
// Take this opportunity to enforce uniform naming
final String targetName;
if (apk.splitName == null) {
targetName = "base" + APK_FILE_EXTENSION;
} else {
targetName = "split_" + apk.splitName + APK_FILE_EXTENSION;
}
final String fileName = splitNameToFileName(apk.splitName);
final String targetName = fileName + APK_FILE_EXTENSION;
if (!FileUtils.isValidExtFilename(targetName)) {
throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
"Invalid filename: " + targetName);
}
final File targetFile = new File(stageDir, targetName);
resolveAndStageFileLocked(addedFile, targetFile);
resolveAndStageFileLocked(addedFile, targetFile, apk.splitName);
// Base is coming from session
if (apk.splitName == null) {
mResolvedBaseFile = targetFile;
baseApk = apk;
}
// Validate and add Dex Metadata (.dm).
final File dexMetadataFile = DexMetadataHelper.findDexMetadataForFile(addedFile);
if (dexMetadataFile != null) {
if (!FileUtils.isValidExtFilename(dexMetadataFile.getName())) {
throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
"Invalid filename: " + dexMetadataFile);
}
final File targetDexMetadataFile = new File(stageDir,
DexMetadataHelper.buildDexMetadataPathForApk(targetName));
resolveAndStageFileLocked(dexMetadataFile, targetDexMetadataFile);
}
}
if (removeSplitList.size() > 0) {
@@ -2338,15 +2406,20 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
}
if (!mChecksums.isEmpty()) {
throw new PackageManagerException(
PackageManager.INSTALL_FAILED_SESSION_INVALID,
"Invalid checksum name(s): " + String.join(",", mChecksums.keySet()));
}
if (params.mode == SessionParams.MODE_FULL_INSTALL) {
// Full installs must include a base package
if (!stagedSplits.contains(null)) {
throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
"Full install must include a base package");
}
} else {
ApplicationInfo appInfo = pkgInfo.applicationInfo;
final ApplicationInfo appInfo = pkgInfo.applicationInfo;
ParseResult<PackageLite> pkgLiteResult = ApkLiteParseUtils.parsePackageLite(
input.reset(), new File(appInfo.getCodePath()), 0);
if (pkgLiteResult.isError()) {
@@ -2365,33 +2438,21 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
assertApkConsistentLocked("Existing base", existingBase);
// Inherit base if not overridden
// Inherit base if not overridden.
if (mResolvedBaseFile == null) {
mResolvedBaseFile = new File(appInfo.getBaseCodePath());
resolveInheritedFileLocked(mResolvedBaseFile);
// Inherit the dex metadata if present.
final File baseDexMetadataFile =
DexMetadataHelper.findDexMetadataForFile(mResolvedBaseFile);
if (baseDexMetadataFile != null) {
resolveInheritedFileLocked(baseDexMetadataFile);
}
inheritFileLocked(mResolvedBaseFile);
baseApk = existingBase;
}
// Inherit splits if not overridden
// Inherit splits if not overridden.
if (!ArrayUtils.isEmpty(existing.splitNames)) {
for (int i = 0; i < existing.splitNames.length; i++) {
final String splitName = existing.splitNames[i];
final File splitFile = new File(existing.splitCodePaths[i]);
final boolean splitRemoved = removeSplitList.contains(splitName);
if (!stagedSplits.contains(splitName) && !splitRemoved) {
resolveInheritedFileLocked(splitFile);
// Inherit the dex metadata if present.
final File splitDexMetadataFile =
DexMetadataHelper.findDexMetadataForFile(splitFile);
if (splitDexMetadataFile != null) {
resolveInheritedFileLocked(splitDexMetadataFile);
}
inheritFileLocked(splitFile);
}
}
}
@@ -2492,11 +2553,15 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
@GuardedBy("mLock")
private void resolveAndStageFileLocked(File origFile, File targetFile)
private void stageFileLocked(File origFile, File targetFile)
throws PackageManagerException {
mResolvedStagedFiles.add(targetFile);
maybeRenameFile(origFile, targetFile);
}
@GuardedBy("mLock")
private void maybeStageFsveritySignatureLocked(File origFile, File targetFile)
throws PackageManagerException {
final File originalSignature = new File(
VerityUtils.getFsveritySignatureFilePath(origFile.getPath()));
// Make sure .fsv_sig exists when it should, then resolve and stage it.
@@ -2521,14 +2586,84 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
final File stagedSignature = new File(
VerityUtils.getFsveritySignatureFilePath(targetFile.getPath()));
maybeRenameFile(originalSignature, stagedSignature);
mResolvedStagedFiles.add(stagedSignature);
stageFileLocked(originalSignature, stagedSignature);
}
@GuardedBy("mLock")
private void resolveInheritedFileLocked(File origFile) {
mResolvedInheritedFiles.add(origFile);
private void maybeStageDexMetadataLocked(File origFile, File targetFile)
throws PackageManagerException {
final File dexMetadataFile = DexMetadataHelper.findDexMetadataForFile(origFile);
if (dexMetadataFile == null) {
return;
}
if (!FileUtils.isValidExtFilename(dexMetadataFile.getName())) {
throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
"Invalid filename: " + dexMetadataFile);
}
final File targetDexMetadataFile = new File(stageDir,
DexMetadataHelper.buildDexMetadataPathForApk(targetFile.getName()));
stageFileLocked(dexMetadataFile, targetDexMetadataFile);
maybeStageFsveritySignatureLocked(dexMetadataFile, targetDexMetadataFile);
}
private static ApkChecksum[] createApkChecksums(String splitName,
List<CertifiedChecksum> checksums) {
ApkChecksum[] result = new ApkChecksum[checksums.size()];
for (int i = 0, size = checksums.size(); i < size; ++i) {
CertifiedChecksum checksum = checksums.get(i);
result[i] = new ApkChecksum(splitName, checksum.getChecksum(),
checksum.getCertificate());
}
return result;
}
@GuardedBy("mLock")
private void maybeStageDigestsLocked(File origFile, File targetFile, String splitName)
throws PackageManagerException {
final List<CertifiedChecksum> checksums = mChecksums.get(origFile.getName());
if (checksums == null) {
return;
}
mChecksums.remove(origFile.getName());
if (checksums.isEmpty()) {
return;
}
final File targetDigestsFile = new File(stageDir,
ApkChecksums.buildDigestsPathForApk(targetFile.getName()));
try {
ApkChecksums.writeChecksums(targetDigestsFile,
createApkChecksums(splitName, checksums));
} catch (CertificateException e) {
throw new PackageManagerException(INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING,
"Failed to encode certificate for " + mPackageName, e);
} catch (IOException e) {
throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
"Failed to store digests for " + mPackageName, e);
}
stageFileLocked(targetDigestsFile, targetDigestsFile);
}
@GuardedBy("mLock")
private void resolveAndStageFileLocked(File origFile, File targetFile, String splitName)
throws PackageManagerException {
stageFileLocked(origFile, targetFile);
// Stage fsverity signature if present.
maybeStageFsveritySignatureLocked(origFile, targetFile);
// Stage dex metadata (.dm) if present.
maybeStageDexMetadataLocked(origFile, targetFile);
// Stage checksums (.digests) if present.
maybeStageDigestsLocked(origFile, targetFile, splitName);
}
@GuardedBy("mLock")
private void maybeInheritFsveritySignatureLocked(File origFile) {
// Inherit the fsverity signature file if present.
final File fsveritySignatureFile = new File(
VerityUtils.getFsveritySignatureFilePath(origFile.getPath()));
@@ -2537,6 +2672,26 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
}
@GuardedBy("mLock")
private void inheritFileLocked(File origFile) {
mResolvedInheritedFiles.add(origFile);
maybeInheritFsveritySignatureLocked(origFile);
// Inherit the dex metadata if present.
final File dexMetadataFile =
DexMetadataHelper.findDexMetadataForFile(origFile);
if (dexMetadataFile != null) {
mResolvedInheritedFiles.add(dexMetadataFile);
maybeInheritFsveritySignatureLocked(dexMetadataFile);
}
// Inherit the digests if present.
final File digestsFile = ApkChecksums.findDigestsForFile(origFile);
if (digestsFile != null) {
mResolvedInheritedFiles.add(digestsFile);
}
}
@GuardedBy("mLock")
private void assertApkConsistentLocked(String tag, ApkLite apk)
throws PackageManagerException {
@@ -3791,6 +3946,22 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
writeByteArrayAttribute(out, ATTR_SIGNATURE, file.getSignature());
out.endTag(null, TAG_SESSION_FILE);
}
for (int i = 0, isize = mChecksums.size(); i < isize; ++i) {
String fileName = mChecksums.keyAt(i);
List<CertifiedChecksum> checksums = mChecksums.valueAt(i);
for (int j = 0, jsize = checksums.size(); j < jsize; ++j) {
CertifiedChecksum checksum = checksums.get(j);
out.startTag(null, TAG_SESSION_CHECKSUM);
writeStringAttribute(out, ATTR_NAME, fileName);
writeIntAttribute(out, ATTR_CHECKSUM_KIND, checksum.getChecksum().getKind());
writeByteArrayAttribute(out, ATTR_CHECKSUM_VALUE,
checksum.getChecksum().getValue());
writeByteArrayAttribute(out, ATTR_CHECKSUM_CERTIFICATE,
checksum.getCertificate());
out.endTag(null, TAG_SESSION_CHECKSUM);
}
}
}
out.endTag(null, TAG_SESSION);
@@ -3904,6 +4075,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
int autoRevokePermissionsMode = MODE_DEFAULT;
List<Integer> childSessionIds = new ArrayList<>();
List<InstallationFile> files = new ArrayList<>();
ArrayMap<String, List<CertifiedChecksum>> checksums = new ArrayMap<>();
int outerDepth = in.getDepth();
int type;
while ((type = in.next()) != XmlPullParser.END_DOCUMENT
@@ -3932,6 +4104,20 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
readByteArrayAttribute(in, ATTR_METADATA),
readByteArrayAttribute(in, ATTR_SIGNATURE)));
}
if (TAG_SESSION_CHECKSUM.equals(in.getName())) {
final String fileName = readStringAttribute(in, ATTR_NAME);
final CertifiedChecksum certifiedChecksum = new CertifiedChecksum(
new Checksum(readIntAttribute(in, ATTR_CHECKSUM_KIND, 0),
readByteArrayAttribute(in, ATTR_CHECKSUM_VALUE)),
readByteArrayAttribute(in, ATTR_CHECKSUM_CERTIFICATE));
List<CertifiedChecksum> certifiedChecksums = checksums.get(fileName);
if (certifiedChecksums == null) {
certifiedChecksums = new ArrayList<>();
checksums.put(fileName, certifiedChecksums);
}
certifiedChecksums.add(certifiedChecksum);
}
}
if (grantedRuntimePermissions.size() > 0) {
@@ -3964,7 +4150,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
installOriginatingPackageName, installerPackageName, installerAttributionTag);
return new PackageInstallerSession(callback, context, pm, sessionProvider,
installerThread, stagingManager, sessionId, userId, installerUid,
installSource, params, createdMillis, stageDir, stageCid, fileArray,
installSource, params, createdMillis, stageDir, stageCid, fileArray, checksums,
prepared, committed, destroyed, sealed, childSessionIdsArray, parentSessionId,
isReady, isFailed, isApplied, stagedSessionErrorCode, stagedSessionErrorMessage);
}

View File

@@ -164,6 +164,7 @@ import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.AuxiliaryResolveInfo;
import android.content.pm.ChangedPackages;
import android.content.pm.Checksum;
import android.content.pm.ComponentInfo;
import android.content.pm.DataLoaderType;
import android.content.pm.FallbackCategoryProvider;
@@ -2459,8 +2460,8 @@ public class PackageManagerService extends IPackageManager.Stub
@Override
public void getChecksums(@NonNull String packageName, boolean includeSplits,
@PackageManager.FileChecksumKind int optional,
@PackageManager.FileChecksumKind int required, @Nullable List trustedInstallers,
@Checksum.Kind int optional,
@Checksum.Kind int required, @Nullable List trustedInstallers,
@NonNull IntentSender statusReceiver, int userId) {
Objects.requireNonNull(packageName);
Objects.requireNonNull(statusReceiver);

View File

@@ -176,6 +176,7 @@ public class PackageInstallerSessionTest {
/* stageDir */ mTmpDir,
/* stageCid */ null,
/* files */ null,
/* checksums */ null,
/* prepared */ true,
/* committed */ true,
/* destroyed */ staged ? true : false,