Merge "APK digest API and initial implementation."
This commit is contained in:
committed by
Android (Google) Code Review
commit
af7f8f199e
@@ -11617,6 +11617,16 @@ 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();
|
||||
@@ -11992,6 +12002,7 @@ package android.content.pm {
|
||||
method @Nullable public abstract android.graphics.drawable.Drawable getApplicationLogo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
|
||||
method @NonNull public CharSequence getBackgroundPermissionOptionLabel();
|
||||
method @Nullable public abstract android.content.pm.ChangedPackages getChangedPackages(@IntRange(from=0) int);
|
||||
method public void getChecksums(@NonNull String, boolean, int, @Nullable java.util.List<java.security.cert.Certificate>, @NonNull android.content.IntentSender) throws java.security.cert.CertificateEncodingException, java.io.IOException, android.content.pm.PackageManager.NameNotFoundException;
|
||||
method public abstract int getComponentEnabledSetting(@NonNull android.content.ComponentName);
|
||||
method @NonNull public abstract android.graphics.drawable.Drawable getDefaultActivityIcon();
|
||||
method @Nullable public abstract android.graphics.drawable.Drawable getDrawable(@NonNull String, @DrawableRes int, @Nullable android.content.pm.ApplicationInfo);
|
||||
@@ -12082,6 +12093,7 @@ package android.content.pm {
|
||||
field public static final int COMPONENT_ENABLED_STATE_DISABLED_USER = 3; // 0x3
|
||||
field public static final int COMPONENT_ENABLED_STATE_ENABLED = 1; // 0x1
|
||||
field public static final int DONT_KILL_APP = 1; // 0x1
|
||||
field public static final String EXTRA_CHECKSUMS = "android.content.pm.extra.CHECKSUMS";
|
||||
field public static final String EXTRA_VERIFICATION_ID = "android.content.pm.extra.VERIFICATION_ID";
|
||||
field public static final String EXTRA_VERIFICATION_RESULT = "android.content.pm.extra.VERIFICATION_RESULT";
|
||||
field public static final String FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS = "android.software.activities_on_secondary_displays";
|
||||
@@ -12236,6 +12248,8 @@ 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
|
||||
@@ -12245,9 +12259,16 @@ package android.content.pm {
|
||||
field public static final int SIGNATURE_SECOND_NOT_SIGNED = -2; // 0xfffffffe
|
||||
field public static final int SIGNATURE_UNKNOWN_PACKAGE = -4; // 0xfffffffc
|
||||
field public static final int SYNCHRONOUS = 2; // 0x2
|
||||
field @Nullable public static final java.util.List<java.security.cert.Certificate> TRUST_ALL;
|
||||
field @NonNull public static final java.util.List<java.security.cert.Certificate> TRUST_NONE;
|
||||
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 {
|
||||
|
||||
@@ -73,6 +73,7 @@ import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.ParcelableException;
|
||||
import android.os.PersistableBundle;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteException;
|
||||
@@ -107,7 +108,11 @@ import dalvik.system.VMRuntime;
|
||||
|
||||
import libcore.util.EmptyArray;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@@ -135,6 +140,12 @@ public class ApplicationPackageManager extends PackageManager {
|
||||
// Default flags to use with PackageManager when no flags are given.
|
||||
private static final int sDefaultFlags = GET_SHARED_LIBRARY_FILES;
|
||||
|
||||
/** Default set of checksums - includes all available checksums.
|
||||
* @see PackageManager#getChecksums */
|
||||
private static final int DEFAULT_CHECKSUMS =
|
||||
WHOLE_MERKLE_ROOT_4K_SHA256 | WHOLE_MD5 | WHOLE_SHA1 | WHOLE_SHA256 | WHOLE_SHA512
|
||||
| PARTIAL_MERKLE_ROOT_1M_SHA256 | PARTIAL_MERKLE_ROOT_1M_SHA512;
|
||||
|
||||
// Name of the resource which provides background permission button string
|
||||
public static final String APP_PERMISSION_BUTTON_ALLOW_ALWAYS =
|
||||
"app_permission_button_allow_always";
|
||||
@@ -945,6 +956,39 @@ public class ApplicationPackageManager extends PackageManager {
|
||||
}
|
||||
}
|
||||
|
||||
private static List<byte[]> encodeCertificates(List<Certificate> certs) throws
|
||||
CertificateEncodingException {
|
||||
if (certs == null) {
|
||||
return null;
|
||||
}
|
||||
List<byte[]> result = new ArrayList<>(certs.size());
|
||||
for (Certificate cert : certs) {
|
||||
if (!(cert instanceof X509Certificate)) {
|
||||
throw new CertificateEncodingException("Only X509 certificates supported.");
|
||||
}
|
||||
result.add(cert.getEncoded());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getChecksums(@NonNull String packageName, boolean includeSplits,
|
||||
@FileChecksumKind int required, @Nullable List<Certificate> trustedInstallers,
|
||||
@NonNull IntentSender statusReceiver)
|
||||
throws CertificateEncodingException, IOException, NameNotFoundException {
|
||||
Objects.requireNonNull(packageName);
|
||||
Objects.requireNonNull(statusReceiver);
|
||||
try {
|
||||
mPM.getChecksums(packageName, includeSplits, DEFAULT_CHECKSUMS, required,
|
||||
encodeCertificates(trustedInstallers), statusReceiver, getUserId());
|
||||
} catch (ParcelableException e) {
|
||||
e.maybeRethrow(PackageManager.NameNotFoundException.class);
|
||||
throw new RuntimeException(e);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the cached value in a class that does deep compares on string
|
||||
* arrays. The comparison is needed only for the verification mode of
|
||||
|
||||
20
core/java/android/content/pm/FileChecksum.aidl
Normal file
20
core/java/android/content/pm/FileChecksum.aidl
Normal 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 FileChecksum;
|
||||
|
||||
238
core/java/android/content/pm/FileChecksum.java
Normal file
238
core/java/android/content/pm/FileChecksum.java
Normal file
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* 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.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.IntentSender;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.android.internal.util.DataClass;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A typed checksum.
|
||||
*
|
||||
* @see PackageManager#getChecksums(String, boolean, int, List, IntentSender)
|
||||
*/
|
||||
@DataClass(genHiddenConstructor = true)
|
||||
public final class FileChecksum implements Parcelable {
|
||||
/**
|
||||
* Checksum for which split. Null indicates base.apk.
|
||||
*/
|
||||
private final @Nullable String mSplitName;
|
||||
/**
|
||||
* Checksum kind.
|
||||
*/
|
||||
private final @PackageManager.FileChecksumKind int mKind;
|
||||
/**
|
||||
* Checksum value.
|
||||
*/
|
||||
private final @NonNull byte[] mValue;
|
||||
/**
|
||||
* For Installer-provided checksums, certificate of the Installer/AppStore.
|
||||
*/
|
||||
private final @Nullable byte[] mSourceCertificate;
|
||||
|
||||
/**
|
||||
* Constructor, internal use only
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public FileChecksum(@Nullable String splitName, @PackageManager.FileChecksumKind int kind,
|
||||
@NonNull byte[] value) {
|
||||
this(splitName, kind, value, (byte[]) null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor, internal use only
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public FileChecksum(@Nullable String splitName, @PackageManager.FileChecksumKind int kind,
|
||||
@NonNull byte[] value, @Nullable Certificate sourceCertificate)
|
||||
throws CertificateEncodingException {
|
||||
this(splitName, kind, value,
|
||||
(sourceCertificate != null) ? sourceCertificate.getEncoded() : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Certificate of the source of this checksum.
|
||||
* @throws CertificateException in case when certificate can't be re-created from serialized
|
||||
* data.
|
||||
*/
|
||||
public @Nullable Certificate getSourceCertificate() throws CertificateException {
|
||||
if (mSourceCertificate == null) {
|
||||
return null;
|
||||
}
|
||||
final CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||
final InputStream is = new ByteArrayInputStream(mSourceCertificate);
|
||||
final X509Certificate cert = (X509Certificate) cf.generateCertificate(is);
|
||||
return cert;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 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/FileChecksum.java
|
||||
//
|
||||
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
|
||||
// Settings > Editor > Code Style > Formatter Control
|
||||
//@formatter:off
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new FileChecksum.
|
||||
*
|
||||
* @param splitName
|
||||
* Checksum for which split. Null indicates base.apk.
|
||||
* @param kind
|
||||
* Checksum kind.
|
||||
* @param value
|
||||
* Checksum value.
|
||||
* @param sourceCertificate
|
||||
* For Installer-provided checksums, certificate of the Installer/AppStore.
|
||||
* @hide
|
||||
*/
|
||||
@DataClass.Generated.Member
|
||||
public FileChecksum(
|
||||
@Nullable String splitName,
|
||||
@PackageManager.FileChecksumKind int kind,
|
||||
@NonNull byte[] value,
|
||||
@Nullable byte[] sourceCertificate) {
|
||||
this.mSplitName = splitName;
|
||||
this.mKind = kind;
|
||||
com.android.internal.util.AnnotationValidations.validate(
|
||||
PackageManager.FileChecksumKind.class, null, mKind);
|
||||
this.mValue = value;
|
||||
com.android.internal.util.AnnotationValidations.validate(
|
||||
NonNull.class, null, mValue);
|
||||
this.mSourceCertificate = sourceCertificate;
|
||||
|
||||
// onConstructed(); // You can define this method to get a callback
|
||||
}
|
||||
|
||||
/**
|
||||
* Checksum for which split. Null indicates base.apk.
|
||||
*/
|
||||
@DataClass.Generated.Member
|
||||
public @Nullable String getSplitName() {
|
||||
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) {
|
||||
// You can override field parcelling by defining methods like:
|
||||
// void parcelFieldName(Parcel dest, int flags) { ... }
|
||||
|
||||
byte flg = 0;
|
||||
if (mSplitName != null) flg |= 0x1;
|
||||
if (mSourceCertificate != null) flg |= 0x8;
|
||||
dest.writeByte(flg);
|
||||
if (mSplitName != null) dest.writeString(mSplitName);
|
||||
dest.writeInt(mKind);
|
||||
dest.writeByteArray(mValue);
|
||||
if (mSourceCertificate != null) dest.writeByteArray(mSourceCertificate);
|
||||
}
|
||||
|
||||
@Override
|
||||
@DataClass.Generated.Member
|
||||
public int describeContents() { return 0; }
|
||||
|
||||
/** @hide */
|
||||
@SuppressWarnings({"unchecked", "RedundantCast"})
|
||||
@DataClass.Generated.Member
|
||||
/* package-private */ FileChecksum(@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();
|
||||
|
||||
this.mSplitName = splitName;
|
||||
this.mKind = kind;
|
||||
com.android.internal.util.AnnotationValidations.validate(
|
||||
PackageManager.FileChecksumKind.class, null, mKind);
|
||||
this.mValue = value;
|
||||
com.android.internal.util.AnnotationValidations.validate(
|
||||
NonNull.class, null, mValue);
|
||||
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>() {
|
||||
@Override
|
||||
public FileChecksum[] newArray(int size) {
|
||||
return new FileChecksum[size];
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileChecksum createFromParcel(@NonNull Parcel in) {
|
||||
return new FileChecksum(in);
|
||||
}
|
||||
};
|
||||
|
||||
@DataClass.Generated(
|
||||
time = 1598322801861L,
|
||||
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)")
|
||||
@Deprecated
|
||||
private void __metadata() {}
|
||||
|
||||
|
||||
//@formatter:on
|
||||
// End of generated code
|
||||
|
||||
}
|
||||
@@ -743,6 +743,8 @@ interface IPackageManager {
|
||||
|
||||
void notifyPackagesReplacedReceived(in String[] packages);
|
||||
|
||||
void getChecksums(in String packageName, boolean includeSplits, int optional, int required, in List trustedInstallers, in IntentSender statusReceiver, int userId);
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
//
|
||||
// The following binder interfaces have been moved to IPermissionManager
|
||||
|
||||
@@ -79,8 +79,11 @@ import com.android.internal.util.ArrayUtils;
|
||||
import dalvik.system.VMRuntime;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -3305,6 +3308,13 @@ public abstract class PackageManager {
|
||||
public static final String EXTRA_FAILURE_EXISTING_PERMISSION
|
||||
= "android.content.pm.extra.FAILURE_EXISTING_PERMISSION";
|
||||
|
||||
/**
|
||||
* Extra field name for the ID of a package pending verification. Passed to
|
||||
* a package verifier and is used to call back to
|
||||
* @see #getChecksums
|
||||
*/
|
||||
public static final String EXTRA_CHECKSUMS = "android.content.pm.extra.CHECKSUMS";
|
||||
|
||||
/**
|
||||
* Permission flag: The permission is set in its current state
|
||||
* by the user and apps can still request it at runtime.
|
||||
@@ -7841,6 +7851,114 @@ 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
|
||||
*/
|
||||
public static final @Nullable List<Certificate> TRUST_ALL = null;
|
||||
|
||||
/**
|
||||
* Don't trust any Installer to provide checksums for the package.
|
||||
* This effectively disables optimized Installer-enforced checksums.
|
||||
* @see #getChecksums
|
||||
*/
|
||||
public static final @NonNull List<Certificate> TRUST_NONE = Collections.emptyList();
|
||||
|
||||
/**
|
||||
* Returns the checksums for APKs within a package.
|
||||
*
|
||||
* By default returns all readily available checksums:
|
||||
* - enforced by platform,
|
||||
* - enforced by installer.
|
||||
* If caller needs a specific checksum kind, they can specify it as required.
|
||||
*
|
||||
* @param packageName whose checksums to return.
|
||||
* @param includeSplits whether to include checksums for non-base splits.
|
||||
* @param required explicitly request the checksum kinds. Will incur significant
|
||||
* CPU/memory/disk usage.
|
||||
* @param trustedInstallers for checksums enforced by Installer, which ones to be trusted.
|
||||
* {@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[].
|
||||
* @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,
|
||||
@NonNull IntentSender statusReceiver)
|
||||
throws CertificateEncodingException, IOException, NameNotFoundException {
|
||||
throw new UnsupportedOperationException("getChecksums not implemented in subclass");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the default text classifier package name, or null if there's none.
|
||||
*
|
||||
|
||||
@@ -149,7 +149,7 @@ public class ApkSignatureSchemeV2Verifier {
|
||||
* @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2.
|
||||
* @throws IOException if an I/O error occurs while reading the APK file.
|
||||
*/
|
||||
private static SignatureInfo findSignature(RandomAccessFile apk)
|
||||
public static SignatureInfo findSignature(RandomAccessFile apk)
|
||||
throws IOException, SignatureNotFoundException {
|
||||
return ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V2_BLOCK_ID);
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ public class ApkSignatureSchemeV3Verifier {
|
||||
* @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v3.
|
||||
* @throws IOException if an I/O error occurs while reading the APK file.
|
||||
*/
|
||||
private static SignatureInfo findSignature(RandomAccessFile apk)
|
||||
public static SignatureInfo findSignature(RandomAccessFile apk)
|
||||
throws IOException, SignatureNotFoundException {
|
||||
return ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V3_BLOCK_ID);
|
||||
}
|
||||
|
||||
@@ -92,6 +92,20 @@ public class ApkSignatureVerifier {
|
||||
private static PackageParser.SigningDetails verifySignatures(String apkPath,
|
||||
@SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull)
|
||||
throws PackageParserException {
|
||||
return verifySignaturesInternal(apkPath, minSignatureSchemeVersion,
|
||||
verifyFull).signingDetails;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the provided APK using all allowed signing schemas.
|
||||
* @return the certificates associated with each signer and content digests.
|
||||
* @param verifyFull whether to verify all contents of this APK or just collect certificates.
|
||||
* @throws PackageParserException if there was a problem collecting certificates
|
||||
* @hide
|
||||
*/
|
||||
public static SigningDetailsWithDigests verifySignaturesInternal(String apkPath,
|
||||
@SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull)
|
||||
throws PackageParserException {
|
||||
|
||||
if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V4) {
|
||||
// V3 and before are older than the requested minimum signing version
|
||||
@@ -121,7 +135,7 @@ public class ApkSignatureVerifier {
|
||||
return verifyV3AndBelowSignatures(apkPath, minSignatureSchemeVersion, verifyFull);
|
||||
}
|
||||
|
||||
private static PackageParser.SigningDetails verifyV3AndBelowSignatures(String apkPath,
|
||||
private static SigningDetailsWithDigests verifyV3AndBelowSignatures(String apkPath,
|
||||
@SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull)
|
||||
throws PackageParserException {
|
||||
// try v3
|
||||
@@ -174,7 +188,7 @@ public class ApkSignatureVerifier {
|
||||
* @throws SignatureNotFoundException if there are no V4 signatures in the APK
|
||||
* @throws PackageParserException if there was a problem collecting certificates
|
||||
*/
|
||||
private static PackageParser.SigningDetails verifyV4Signature(String apkPath,
|
||||
private static SigningDetailsWithDigests verifyV4Signature(String apkPath,
|
||||
@SignatureSchemeVersion int minSignatureSchemeVersion, boolean verifyFull)
|
||||
throws SignatureNotFoundException, PackageParserException {
|
||||
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, verifyFull ? "verifyV4" : "certsOnlyV4");
|
||||
@@ -234,8 +248,8 @@ public class ApkSignatureVerifier {
|
||||
}
|
||||
}
|
||||
|
||||
return new PackageParser.SigningDetails(signerSigs,
|
||||
SignatureSchemeVersion.SIGNING_BLOCK_V4);
|
||||
return new SigningDetailsWithDigests(new PackageParser.SigningDetails(signerSigs,
|
||||
SignatureSchemeVersion.SIGNING_BLOCK_V4), vSigner.contentDigests);
|
||||
} catch (SignatureNotFoundException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
@@ -256,8 +270,8 @@ public class ApkSignatureVerifier {
|
||||
* @throws SignatureNotFoundException if there are no V3 signatures in the APK
|
||||
* @throws PackageParserException if there was a problem collecting certificates
|
||||
*/
|
||||
private static PackageParser.SigningDetails verifyV3Signature(String apkPath,
|
||||
boolean verifyFull) throws SignatureNotFoundException, PackageParserException {
|
||||
private static SigningDetailsWithDigests verifyV3Signature(String apkPath, boolean verifyFull)
|
||||
throws SignatureNotFoundException, PackageParserException {
|
||||
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, verifyFull ? "verifyV3" : "certsOnlyV3");
|
||||
try {
|
||||
ApkSignatureSchemeV3Verifier.VerifiedSigner vSigner =
|
||||
@@ -275,8 +289,9 @@ public class ApkSignatureVerifier {
|
||||
pastSignerSigs[i].setFlags(vSigner.por.flagsList.get(i));
|
||||
}
|
||||
}
|
||||
return new PackageParser.SigningDetails(signerSigs,
|
||||
SignatureSchemeVersion.SIGNING_BLOCK_V3, pastSignerSigs);
|
||||
return new SigningDetailsWithDigests(new PackageParser.SigningDetails(signerSigs,
|
||||
SignatureSchemeVersion.SIGNING_BLOCK_V3, pastSignerSigs),
|
||||
vSigner.contentDigests);
|
||||
} catch (SignatureNotFoundException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
@@ -297,15 +312,16 @@ public class ApkSignatureVerifier {
|
||||
* @throws SignatureNotFoundException if there are no V2 signatures in the APK
|
||||
* @throws PackageParserException if there was a problem collecting certificates
|
||||
*/
|
||||
private static PackageParser.SigningDetails verifyV2Signature(String apkPath,
|
||||
boolean verifyFull) throws SignatureNotFoundException, PackageParserException {
|
||||
private static SigningDetailsWithDigests verifyV2Signature(String apkPath, boolean verifyFull)
|
||||
throws SignatureNotFoundException, PackageParserException {
|
||||
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, verifyFull ? "verifyV2" : "certsOnlyV2");
|
||||
try {
|
||||
Certificate[][] signerCerts = verifyFull ? ApkSignatureSchemeV2Verifier.verify(apkPath)
|
||||
: ApkSignatureSchemeV2Verifier.unsafeGetCertsWithoutVerification(apkPath);
|
||||
ApkSignatureSchemeV2Verifier.VerifiedSigner vSigner =
|
||||
ApkSignatureSchemeV2Verifier.verify(apkPath, verifyFull);
|
||||
Certificate[][] signerCerts = vSigner.certs;
|
||||
Signature[] signerSigs = convertToSignatures(signerCerts);
|
||||
return new PackageParser.SigningDetails(signerSigs,
|
||||
SignatureSchemeVersion.SIGNING_BLOCK_V2);
|
||||
return new SigningDetailsWithDigests(new PackageParser.SigningDetails(signerSigs,
|
||||
SignatureSchemeVersion.SIGNING_BLOCK_V2), vSigner.contentDigests);
|
||||
} catch (SignatureNotFoundException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
@@ -324,8 +340,7 @@ public class ApkSignatureVerifier {
|
||||
* @param verifyFull whether to verify all contents of this APK or just collect certificates.
|
||||
* @throws PackageParserException if there was a problem collecting certificates
|
||||
*/
|
||||
private static PackageParser.SigningDetails verifyV1Signature(
|
||||
String apkPath, boolean verifyFull)
|
||||
private static SigningDetailsWithDigests verifyV1Signature(String apkPath, boolean verifyFull)
|
||||
throws PackageParserException {
|
||||
StrictJarFile jarFile = null;
|
||||
|
||||
@@ -391,7 +406,8 @@ public class ApkSignatureVerifier {
|
||||
}
|
||||
}
|
||||
}
|
||||
return new PackageParser.SigningDetails(lastSigs, SignatureSchemeVersion.JAR);
|
||||
return new SigningDetailsWithDigests(
|
||||
new PackageParser.SigningDetails(lastSigs, SignatureSchemeVersion.JAR), null);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new PackageParserException(INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING,
|
||||
"Failed to collect certificates from " + apkPath, e);
|
||||
@@ -542,4 +558,27 @@ public class ApkSignatureVerifier {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extended signing details.
|
||||
* @hide for internal use only.
|
||||
*/
|
||||
public static class SigningDetailsWithDigests {
|
||||
public final PackageParser.SigningDetails signingDetails;
|
||||
|
||||
/**
|
||||
* APK Signature Schemes v2/v3/v4 might contain multiple content digests.
|
||||
* SignatureVerifier usually chooses one of them to verify.
|
||||
* For certain signature schemes, e.g. v4, this digest is verified continuously.
|
||||
* For others, e.g. v2, the caller has to specify if they want to verify.
|
||||
* Please refer to documentation for more details.
|
||||
*/
|
||||
public final Map<Integer, byte[]> contentDigests;
|
||||
|
||||
SigningDetailsWithDigests(PackageParser.SigningDetails signingDetails,
|
||||
Map<Integer, byte[]> contentDigests) {
|
||||
this.signingDetails = signingDetails;
|
||||
this.contentDigests = contentDigests;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ import java.util.Map;
|
||||
*
|
||||
* @hide for internal use only.
|
||||
*/
|
||||
final class ApkSigningBlockUtils {
|
||||
public final class ApkSigningBlockUtils {
|
||||
|
||||
private ApkSigningBlockUtils() {
|
||||
}
|
||||
@@ -146,6 +146,37 @@ final class ApkSigningBlockUtils {
|
||||
Map<Integer, byte[]> expectedDigests,
|
||||
FileDescriptor apkFileDescriptor,
|
||||
SignatureInfo signatureInfo) throws SecurityException {
|
||||
int[] digestAlgorithms = new int[expectedDigests.size()];
|
||||
int digestAlgorithmCount = 0;
|
||||
for (int digestAlgorithm : expectedDigests.keySet()) {
|
||||
digestAlgorithms[digestAlgorithmCount] = digestAlgorithm;
|
||||
digestAlgorithmCount++;
|
||||
}
|
||||
byte[][] actualDigests;
|
||||
try {
|
||||
actualDigests = computeContentDigestsPer1MbChunk(digestAlgorithms, apkFileDescriptor,
|
||||
signatureInfo);
|
||||
} catch (DigestException e) {
|
||||
throw new SecurityException("Failed to compute digest(s) of contents", e);
|
||||
}
|
||||
for (int i = 0; i < digestAlgorithms.length; i++) {
|
||||
int digestAlgorithm = digestAlgorithms[i];
|
||||
byte[] expectedDigest = expectedDigests.get(digestAlgorithm);
|
||||
byte[] actualDigest = actualDigests[i];
|
||||
if (!MessageDigest.isEqual(expectedDigest, actualDigest)) {
|
||||
throw new SecurityException(
|
||||
getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm)
|
||||
+ " digest of contents did not verify");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate digests using digestAlgorithms for apkFileDescriptor.
|
||||
* This will skip signature block described by signatureInfo.
|
||||
*/
|
||||
public static byte[][] computeContentDigestsPer1MbChunk(int[] digestAlgorithms,
|
||||
FileDescriptor apkFileDescriptor, SignatureInfo signatureInfo) throws DigestException {
|
||||
// We need to verify the integrity of the following three sections of the file:
|
||||
// 1. Everything up to the start of the APK Signing Block.
|
||||
// 2. ZIP Central Directory.
|
||||
@@ -156,6 +187,7 @@ final class ApkSigningBlockUtils {
|
||||
// avoid wasting physical memory. In most APK verification scenarios, the contents of the
|
||||
// APK are already there in the OS's page cache and thus mmap does not use additional
|
||||
// physical memory.
|
||||
|
||||
DataSource beforeApkSigningBlock =
|
||||
new MemoryMappedFileDataSource(apkFileDescriptor, 0,
|
||||
signatureInfo.apkSigningBlockOffset);
|
||||
@@ -171,31 +203,8 @@ final class ApkSigningBlockUtils {
|
||||
ZipUtils.setZipEocdCentralDirectoryOffset(eocdBuf, signatureInfo.apkSigningBlockOffset);
|
||||
DataSource eocd = new ByteBufferDataSource(eocdBuf);
|
||||
|
||||
int[] digestAlgorithms = new int[expectedDigests.size()];
|
||||
int digestAlgorithmCount = 0;
|
||||
for (int digestAlgorithm : expectedDigests.keySet()) {
|
||||
digestAlgorithms[digestAlgorithmCount] = digestAlgorithm;
|
||||
digestAlgorithmCount++;
|
||||
}
|
||||
byte[][] actualDigests;
|
||||
try {
|
||||
actualDigests =
|
||||
computeContentDigestsPer1MbChunk(
|
||||
digestAlgorithms,
|
||||
new DataSource[] {beforeApkSigningBlock, centralDir, eocd});
|
||||
} catch (DigestException e) {
|
||||
throw new SecurityException("Failed to compute digest(s) of contents", e);
|
||||
}
|
||||
for (int i = 0; i < digestAlgorithms.length; i++) {
|
||||
int digestAlgorithm = digestAlgorithms[i];
|
||||
byte[] expectedDigest = expectedDigests.get(digestAlgorithm);
|
||||
byte[] actualDigest = actualDigests[i];
|
||||
if (!MessageDigest.isEqual(expectedDigest, actualDigest)) {
|
||||
throw new SecurityException(
|
||||
getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm)
|
||||
+ " digest of contents did not verify");
|
||||
}
|
||||
}
|
||||
return computeContentDigestsPer1MbChunk(digestAlgorithms,
|
||||
new DataSource[]{beforeApkSigningBlock, centralDir, eocd});
|
||||
}
|
||||
|
||||
private static byte[][] computeContentDigestsPer1MbChunk(
|
||||
@@ -417,14 +426,10 @@ final class ApkSigningBlockUtils {
|
||||
static final int SIGNATURE_VERITY_ECDSA_WITH_SHA256 = 0x0423;
|
||||
static final int SIGNATURE_VERITY_DSA_WITH_SHA256 = 0x0425;
|
||||
|
||||
static final int CONTENT_DIGEST_CHUNKED_SHA256 = 1;
|
||||
static final int CONTENT_DIGEST_CHUNKED_SHA512 = 2;
|
||||
static final int CONTENT_DIGEST_VERITY_CHUNKED_SHA256 = 3;
|
||||
static final int CONTENT_DIGEST_SHA256 = 4;
|
||||
|
||||
private static final int[] V4_CONTENT_DIGEST_ALGORITHMS =
|
||||
{CONTENT_DIGEST_CHUNKED_SHA512, CONTENT_DIGEST_VERITY_CHUNKED_SHA256,
|
||||
CONTENT_DIGEST_CHUNKED_SHA256};
|
||||
public static final int CONTENT_DIGEST_CHUNKED_SHA256 = 1;
|
||||
public static final int CONTENT_DIGEST_CHUNKED_SHA512 = 2;
|
||||
public static final int CONTENT_DIGEST_VERITY_CHUNKED_SHA256 = 3;
|
||||
public static final int CONTENT_DIGEST_SHA256 = 4;
|
||||
|
||||
static int compareSignatureAlgorithm(int sigAlgorithm1, int sigAlgorithm2) {
|
||||
int digestAlgorithm1 = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm1);
|
||||
|
||||
@@ -16,15 +16,18 @@
|
||||
|
||||
package android.util.apk;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* APK Signature Scheme v2 block and additional information relevant to verifying the signatures
|
||||
* contained in the block against the file.
|
||||
* @hide
|
||||
*/
|
||||
class SignatureInfo {
|
||||
public class SignatureInfo {
|
||||
/** Contents of APK Signature Scheme v2 block. */
|
||||
public final ByteBuffer signatureBlock;
|
||||
public final @NonNull ByteBuffer signatureBlock;
|
||||
|
||||
/** Position of the APK Signing Block in the file. */
|
||||
public final long apkSigningBlockOffset;
|
||||
@@ -36,10 +39,10 @@ class SignatureInfo {
|
||||
public final long eocdOffset;
|
||||
|
||||
/** Contents of ZIP End of Central Directory (EoCD) of the file. */
|
||||
public final ByteBuffer eocd;
|
||||
public final @NonNull ByteBuffer eocd;
|
||||
|
||||
SignatureInfo(ByteBuffer signatureBlock, long apkSigningBlockOffset, long centralDirOffset,
|
||||
long eocdOffset, ByteBuffer eocd) {
|
||||
SignatureInfo(@NonNull ByteBuffer signatureBlock, long apkSigningBlockOffset,
|
||||
long centralDirOffset, long eocdOffset, @NonNull ByteBuffer eocd) {
|
||||
this.signatureBlock = signatureBlock;
|
||||
this.apkSigningBlockOffset = apkSigningBlockOffset;
|
||||
this.centralDirOffset = centralDirOffset;
|
||||
|
||||
@@ -115,6 +115,34 @@ public abstract class VerityBuilder {
|
||||
signingBlockSize, signatureInfo.eocdOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the fs-verity hash tree. It is the actual verity tree format on disk, as is
|
||||
* re-generated on device.
|
||||
*
|
||||
* The tree is built bottom up. The bottom level has 256-bit digest for each 4 KB block in the
|
||||
* input file. If the total size is larger than 4 KB, take this level as input and repeat the
|
||||
* same procedure, until the level is within 4 KB. If salt is given, it will apply to each
|
||||
* digestion before the actual data.
|
||||
*
|
||||
* The returned root hash is calculated from the last level of 4 KB chunk, similarly with salt.
|
||||
*
|
||||
* @return the root hash of the generated hash tree.
|
||||
*/
|
||||
public static byte[] generateFsVerityRootHash(@NonNull String apkPath, byte[] salt,
|
||||
@NonNull ByteBufferFactory bufferFactory)
|
||||
throws IOException, NoSuchAlgorithmException, DigestException {
|
||||
try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) {
|
||||
int[] levelOffset = calculateVerityLevelOffset(apk.length());
|
||||
int merkleTreeSize = levelOffset[levelOffset.length - 1];
|
||||
|
||||
ByteBuffer output = bufferFactory.create(
|
||||
merkleTreeSize
|
||||
+ CHUNK_SIZE_BYTES); // maximum size of apk-verity metadata
|
||||
output.order(ByteOrder.LITTLE_ENDIAN);
|
||||
ByteBuffer tree = slice(output, 0, merkleTreeSize);
|
||||
return generateFsVerityTreeInternal(apk, salt, levelOffset, tree);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Calculates the apk-verity root hash for integrity measurement. This needs to be consistent
|
||||
* to what kernel returns.
|
||||
@@ -259,9 +287,10 @@ public abstract class VerityBuilder {
|
||||
// thus the syscall overhead is not too big.
|
||||
private static final int MMAP_REGION_SIZE_BYTES = 1024 * 1024;
|
||||
|
||||
private static void generateFsVerityDigestAtLeafLevel(RandomAccessFile file, ByteBuffer output)
|
||||
private static void generateFsVerityDigestAtLeafLevel(RandomAccessFile file,
|
||||
@Nullable byte[] salt, ByteBuffer output)
|
||||
throws IOException, NoSuchAlgorithmException, DigestException {
|
||||
BufferedDigester digester = new BufferedDigester(null /* salt */, output);
|
||||
BufferedDigester digester = new BufferedDigester(salt, output);
|
||||
|
||||
// 1. Digest the whole file by chunks.
|
||||
consumeByChunk(digester,
|
||||
@@ -324,6 +353,35 @@ public abstract class VerityBuilder {
|
||||
digester.fillUpLastOutputChunk();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static byte[] generateFsVerityTreeInternal(@NonNull RandomAccessFile apk,
|
||||
@Nullable byte[] salt, @NonNull int[] levelOffset, @NonNull ByteBuffer output)
|
||||
throws IOException, NoSuchAlgorithmException, DigestException {
|
||||
// 1. Digest the apk to generate the leaf level hashes.
|
||||
generateFsVerityDigestAtLeafLevel(apk, salt,
|
||||
slice(output, levelOffset[levelOffset.length - 2],
|
||||
levelOffset[levelOffset.length - 1]));
|
||||
|
||||
// 2. Digest the lower level hashes bottom up.
|
||||
for (int level = levelOffset.length - 3; level >= 0; level--) {
|
||||
ByteBuffer inputBuffer = slice(output, levelOffset[level + 1], levelOffset[level + 2]);
|
||||
ByteBuffer outputBuffer = slice(output, levelOffset[level], levelOffset[level + 1]);
|
||||
|
||||
DataSource source = new ByteBufferDataSource(inputBuffer);
|
||||
BufferedDigester digester = new BufferedDigester(salt, outputBuffer);
|
||||
consumeByChunk(digester, source, CHUNK_SIZE_BYTES);
|
||||
digester.assertEmptyBuffer();
|
||||
digester.fillUpLastOutputChunk();
|
||||
}
|
||||
|
||||
// 3. Digest the first block (i.e. first level) to generate the root hash.
|
||||
byte[] rootHash = new byte[DIGEST_SIZE_BYTES];
|
||||
BufferedDigester digester = new BufferedDigester(salt, ByteBuffer.wrap(rootHash));
|
||||
digester.consume(slice(output, 0, CHUNK_SIZE_BYTES));
|
||||
digester.assertEmptyBuffer();
|
||||
return rootHash;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static byte[] generateVerityTreeInternal(@NonNull RandomAccessFile apk,
|
||||
@Nullable SignatureInfo signatureInfo, @Nullable byte[] salt,
|
||||
|
||||
@@ -11617,6 +11617,16 @@ 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();
|
||||
@@ -11992,6 +12002,7 @@ package android.content.pm {
|
||||
method @Nullable public abstract android.graphics.drawable.Drawable getApplicationLogo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
|
||||
method @NonNull public CharSequence getBackgroundPermissionOptionLabel();
|
||||
method @Nullable public abstract android.content.pm.ChangedPackages getChangedPackages(@IntRange(from=0) int);
|
||||
method public void getChecksums(@NonNull String, boolean, int, @Nullable java.util.List<java.security.cert.Certificate>, @NonNull android.content.IntentSender) throws java.security.cert.CertificateEncodingException, java.io.IOException, android.content.pm.PackageManager.NameNotFoundException;
|
||||
method public abstract int getComponentEnabledSetting(@NonNull android.content.ComponentName);
|
||||
method @NonNull public abstract android.graphics.drawable.Drawable getDefaultActivityIcon();
|
||||
method @Nullable public abstract android.graphics.drawable.Drawable getDrawable(@NonNull String, @DrawableRes int, @Nullable android.content.pm.ApplicationInfo);
|
||||
@@ -12082,6 +12093,7 @@ package android.content.pm {
|
||||
field public static final int COMPONENT_ENABLED_STATE_DISABLED_USER = 3; // 0x3
|
||||
field public static final int COMPONENT_ENABLED_STATE_ENABLED = 1; // 0x1
|
||||
field public static final int DONT_KILL_APP = 1; // 0x1
|
||||
field public static final String EXTRA_CHECKSUMS = "android.content.pm.extra.CHECKSUMS";
|
||||
field public static final String EXTRA_VERIFICATION_ID = "android.content.pm.extra.VERIFICATION_ID";
|
||||
field public static final String EXTRA_VERIFICATION_RESULT = "android.content.pm.extra.VERIFICATION_RESULT";
|
||||
field public static final String FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS = "android.software.activities_on_secondary_displays";
|
||||
@@ -12236,6 +12248,8 @@ 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
|
||||
@@ -12245,9 +12259,16 @@ package android.content.pm {
|
||||
field public static final int SIGNATURE_SECOND_NOT_SIGNED = -2; // 0xfffffffe
|
||||
field public static final int SIGNATURE_UNKNOWN_PACKAGE = -4; // 0xfffffffc
|
||||
field public static final int SYNCHRONOUS = 2; // 0x2
|
||||
field @Nullable public static final java.util.List<java.security.cert.Certificate> TRUST_ALL;
|
||||
field @NonNull public static final java.util.List<java.security.cert.Certificate> TRUST_NONE;
|
||||
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 {
|
||||
|
||||
310
services/core/java/com/android/server/pm/ApkChecksums.java
Normal file
310
services/core/java/com/android/server/pm/ApkChecksums.java
Normal file
@@ -0,0 +1,310 @@
|
||||
/*
|
||||
* 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 com.android.server.pm;
|
||||
|
||||
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.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;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.content.pm.FileChecksum;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageParser;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Slog;
|
||||
import android.util.apk.ApkSignatureSchemeV2Verifier;
|
||||
import android.util.apk.ApkSignatureSchemeV3Verifier;
|
||||
import android.util.apk.ApkSignatureSchemeV4Verifier;
|
||||
import android.util.apk.ApkSignatureVerifier;
|
||||
import android.util.apk.ApkSigningBlockUtils;
|
||||
import android.util.apk.ByteBufferFactory;
|
||||
import android.util.apk.SignatureInfo;
|
||||
import android.util.apk.SignatureNotFoundException;
|
||||
import android.util.apk.VerityBuilder;
|
||||
|
||||
import com.android.server.security.VerityUtils;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
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.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Provides checksums for APK.
|
||||
*/
|
||||
public class ApkChecksums {
|
||||
static final String TAG = "ApkChecksums";
|
||||
|
||||
// MessageDigest algorithms.
|
||||
static final String ALGO_MD5 = "MD5";
|
||||
static final String ALGO_SHA1 = "SHA1";
|
||||
static final String ALGO_SHA256 = "SHA256";
|
||||
static final String ALGO_SHA512 = "SHA512";
|
||||
|
||||
/**
|
||||
* Fetch or calculate checksums for the specific file.
|
||||
*
|
||||
* @param split split name, null for base
|
||||
* @param file to fetch checksums for
|
||||
* @param optional mask to fetch readily available checksums
|
||||
* @param required mask to forcefully calculate if not available
|
||||
* @param trustedInstallers array of certificate to trust, two specific cases:
|
||||
* null - trust anybody,
|
||||
* [] - trust nobody.
|
||||
*/
|
||||
public static List<FileChecksum> getFileChecksums(String split, File file,
|
||||
@PackageManager.FileChecksumKind int optional,
|
||||
@PackageManager.FileChecksumKind int required,
|
||||
@Nullable Certificate[] trustedInstallers) {
|
||||
final String filePath = file.getAbsolutePath();
|
||||
Map<Integer, FileChecksum> checksums = new ArrayMap<>();
|
||||
final int kinds = (optional | required);
|
||||
// System enforced: FSI or v2/v3/v4 signatures.
|
||||
if ((kinds & WHOLE_MERKLE_ROOT_4K_SHA256) != 0) {
|
||||
// Hashes in fs-verity and IncFS are always verified.
|
||||
FileChecksum checksum = extractHashFromFS(split, filePath);
|
||||
if (checksum != null) {
|
||||
checksums.put(checksum.getKind(), checksum);
|
||||
}
|
||||
}
|
||||
if ((kinds & (PARTIAL_MERKLE_ROOT_1M_SHA256 | PARTIAL_MERKLE_ROOT_1M_SHA512)) != 0) {
|
||||
Map<Integer, FileChecksum> v2v3checksums = extractHashFromV2V3Signature(
|
||||
split, filePath, kinds);
|
||||
if (v2v3checksums != null) {
|
||||
checksums.putAll(v2v3checksums);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(b/160605420): Installer provided.
|
||||
// TODO(b/160605420): Wait for Incremental to be fully loaded.
|
||||
|
||||
// Manually calculating required checksums if not readily available.
|
||||
if ((required & WHOLE_MERKLE_ROOT_4K_SHA256) != 0 && !checksums.containsKey(
|
||||
WHOLE_MERKLE_ROOT_4K_SHA256)) {
|
||||
try {
|
||||
byte[] generatedRootHash = VerityBuilder.generateFsVerityRootHash(
|
||||
filePath, /*salt=*/null,
|
||||
new ByteBufferFactory() {
|
||||
@Override
|
||||
public ByteBuffer create(int capacity) {
|
||||
return ByteBuffer.allocate(capacity);
|
||||
}
|
||||
});
|
||||
checksums.put(WHOLE_MERKLE_ROOT_4K_SHA256,
|
||||
new FileChecksum(split, WHOLE_MERKLE_ROOT_4K_SHA256, generatedRootHash));
|
||||
} catch (IOException | NoSuchAlgorithmException | DigestException e) {
|
||||
Slog.e(TAG, "Error calculating WHOLE_MERKLE_ROOT_4K_SHA256", e);
|
||||
}
|
||||
}
|
||||
|
||||
calculateChecksumIfRequested(checksums, split, file, required, WHOLE_MD5);
|
||||
calculateChecksumIfRequested(checksums, split, file, required, WHOLE_SHA1);
|
||||
calculateChecksumIfRequested(checksums, split, file, required, WHOLE_SHA256);
|
||||
calculateChecksumIfRequested(checksums, split, file, required, WHOLE_SHA512);
|
||||
|
||||
calculatePartialChecksumsIfRequested(checksums, split, file, required);
|
||||
|
||||
return new ArrayList<>(checksums.values());
|
||||
}
|
||||
|
||||
private static FileChecksum 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);
|
||||
}
|
||||
}
|
||||
// v4 next
|
||||
try {
|
||||
ApkSignatureSchemeV4Verifier.VerifiedSigner signer =
|
||||
ApkSignatureSchemeV4Verifier.extractCertificates(filePath);
|
||||
byte[] hash = signer.contentDigests.getOrDefault(CONTENT_DIGEST_VERITY_CHUNKED_SHA256,
|
||||
null);
|
||||
if (hash != null) {
|
||||
return new FileChecksum(split, WHOLE_MERKLE_ROOT_4K_SHA256, hash);
|
||||
}
|
||||
} catch (SignatureNotFoundException e) {
|
||||
// Nothing
|
||||
} catch (SecurityException e) {
|
||||
Slog.e(TAG, "V4 signature error", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Map<Integer, FileChecksum> extractHashFromV2V3Signature(
|
||||
String split, String filePath, int kinds) {
|
||||
Map<Integer, byte[]> contentDigests = null;
|
||||
try {
|
||||
contentDigests = ApkSignatureVerifier.verifySignaturesInternal(filePath,
|
||||
PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2,
|
||||
false).contentDigests;
|
||||
} catch (PackageParser.PackageParserException e) {
|
||||
Slog.e(TAG, "Signature verification error", e);
|
||||
}
|
||||
|
||||
if (contentDigests == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<Integer, FileChecksum> 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));
|
||||
}
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
return checksums;
|
||||
}
|
||||
|
||||
private static String getMessageDigestAlgoForChecksumKind(int kind)
|
||||
throws NoSuchAlgorithmException {
|
||||
switch (kind) {
|
||||
case WHOLE_MD5:
|
||||
return ALGO_MD5;
|
||||
case WHOLE_SHA1:
|
||||
return ALGO_SHA1;
|
||||
case WHOLE_SHA256:
|
||||
return ALGO_SHA256;
|
||||
case WHOLE_SHA512:
|
||||
return ALGO_SHA512;
|
||||
default:
|
||||
throw new NoSuchAlgorithmException("Invalid checksum kind: " + kind);
|
||||
}
|
||||
}
|
||||
|
||||
private static void calculateChecksumIfRequested(Map<Integer, FileChecksum> checksums,
|
||||
String split, File file, int required, int kind) {
|
||||
if ((required & kind) != 0 && !checksums.containsKey(kind)) {
|
||||
final byte[] checksum = getFileChecksum(file, kind);
|
||||
if (checksum != null) {
|
||||
checksums.put(kind, new FileChecksum(split, kind, checksum));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] getFileChecksum(File file, int kind) {
|
||||
try (FileInputStream fis = new FileInputStream(file);
|
||||
BufferedInputStream bis = new BufferedInputStream(fis)) {
|
||||
byte[] dataBytes = new byte[512 * 1024];
|
||||
int nread = 0;
|
||||
|
||||
final String algo = getMessageDigestAlgoForChecksumKind(kind);
|
||||
MessageDigest md = MessageDigest.getInstance(algo);
|
||||
while ((nread = bis.read(dataBytes)) != -1) {
|
||||
md.update(dataBytes, 0, nread);
|
||||
}
|
||||
|
||||
return md.digest();
|
||||
} catch (IOException e) {
|
||||
Slog.e(TAG, "Error reading " + file.getAbsolutePath() + " to compute hash.", e);
|
||||
return null;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
Slog.e(TAG, "Device does not support MessageDigest algorithm", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static int[] getContentDigestAlgos(boolean needSignatureSha256,
|
||||
boolean needSignatureSha512) {
|
||||
if (needSignatureSha256 && needSignatureSha512) {
|
||||
// Signature block present, but no digests???
|
||||
return new int[]{CONTENT_DIGEST_CHUNKED_SHA256, CONTENT_DIGEST_CHUNKED_SHA512};
|
||||
} else if (needSignatureSha256) {
|
||||
return new int[]{CONTENT_DIGEST_CHUNKED_SHA256};
|
||||
} else {
|
||||
return new int[]{CONTENT_DIGEST_CHUNKED_SHA512};
|
||||
}
|
||||
}
|
||||
|
||||
private static int getChecksumKindForContentDigestAlgo(int contentDigestAlgo) {
|
||||
switch (contentDigestAlgo) {
|
||||
case CONTENT_DIGEST_CHUNKED_SHA256:
|
||||
return PARTIAL_MERKLE_ROOT_1M_SHA256;
|
||||
case CONTENT_DIGEST_CHUNKED_SHA512:
|
||||
return PARTIAL_MERKLE_ROOT_1M_SHA512;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private static void calculatePartialChecksumsIfRequested(Map<Integer, FileChecksum> checksums,
|
||||
String split, File file, int required) {
|
||||
boolean needSignatureSha256 =
|
||||
(required & PARTIAL_MERKLE_ROOT_1M_SHA256) != 0 && !checksums.containsKey(
|
||||
PARTIAL_MERKLE_ROOT_1M_SHA256);
|
||||
boolean needSignatureSha512 =
|
||||
(required & PARTIAL_MERKLE_ROOT_1M_SHA512) != 0 && !checksums.containsKey(
|
||||
PARTIAL_MERKLE_ROOT_1M_SHA512);
|
||||
if (!needSignatureSha256 && !needSignatureSha512) {
|
||||
return;
|
||||
}
|
||||
|
||||
try (RandomAccessFile raf = new RandomAccessFile(file, "r")) {
|
||||
SignatureInfo signatureInfo = null;
|
||||
try {
|
||||
signatureInfo = ApkSignatureSchemeV3Verifier.findSignature(raf);
|
||||
} catch (SignatureNotFoundException e) {
|
||||
try {
|
||||
signatureInfo = ApkSignatureSchemeV2Verifier.findSignature(raf);
|
||||
} catch (SignatureNotFoundException ee) {
|
||||
}
|
||||
}
|
||||
if (signatureInfo == null) {
|
||||
Slog.e(TAG, "V2/V3 signatures not found in " + file.getAbsolutePath());
|
||||
return;
|
||||
}
|
||||
|
||||
final int[] digestAlgos = getContentDigestAlgos(needSignatureSha256,
|
||||
needSignatureSha512);
|
||||
byte[][] digests = ApkSigningBlockUtils.computeContentDigestsPer1MbChunk(digestAlgos,
|
||||
raf.getFD(), signatureInfo);
|
||||
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]));
|
||||
}
|
||||
}
|
||||
} catch (IOException | DigestException e) {
|
||||
Slog.e(TAG, "Error computing hash.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,7 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED
|
||||
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
|
||||
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
|
||||
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
|
||||
import static android.content.pm.PackageManager.EXTRA_CHECKSUMS;
|
||||
import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ID;
|
||||
import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
|
||||
import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED;
|
||||
@@ -168,6 +169,7 @@ import android.content.pm.ComponentInfo;
|
||||
import android.content.pm.DataLoaderType;
|
||||
import android.content.pm.FallbackCategoryProvider;
|
||||
import android.content.pm.FeatureInfo;
|
||||
import android.content.pm.FileChecksum;
|
||||
import android.content.pm.IDexModuleRegisterCallback;
|
||||
import android.content.pm.IPackageChangeObserver;
|
||||
import android.content.pm.IPackageDataObserver;
|
||||
@@ -254,6 +256,7 @@ import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.Parcel;
|
||||
import android.os.ParcelableException;
|
||||
import android.os.PatternMatcher;
|
||||
import android.os.PersistableBundle;
|
||||
import android.os.Process;
|
||||
@@ -394,6 +397,7 @@ import java.io.FileDescriptor;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
@@ -404,7 +408,10 @@ import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
@@ -2443,6 +2450,83 @@ public class PackageManagerService extends IPackageManager.Stub
|
||||
mHandler.sendMessageDelayed(message, DEFERRED_NO_KILL_INSTALL_OBSERVER_DELAY_MS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getChecksums(@NonNull String packageName, boolean includeSplits,
|
||||
@PackageManager.FileChecksumKind int optional,
|
||||
@PackageManager.FileChecksumKind int required, @Nullable List trustedInstallers,
|
||||
@NonNull IntentSender statusReceiver, int userId) {
|
||||
Objects.requireNonNull(packageName);
|
||||
Objects.requireNonNull(statusReceiver);
|
||||
|
||||
final ApplicationInfo applicationInfo = getApplicationInfoInternal(packageName, 0,
|
||||
Binder.getCallingUid(), userId);
|
||||
if (applicationInfo == null) {
|
||||
throw new ParcelableException(new PackageManager.NameNotFoundException(packageName));
|
||||
}
|
||||
|
||||
List<Pair<String, File>> filesToChecksum = new ArrayList<>();
|
||||
|
||||
// Adding base split.
|
||||
filesToChecksum.add(Pair.create(null, new File(applicationInfo.sourceDir)));
|
||||
|
||||
// Adding other splits.
|
||||
if (includeSplits && applicationInfo.splitNames != null) {
|
||||
for (int i = 0, size = applicationInfo.splitNames.length; i < size; ++i) {
|
||||
filesToChecksum.add(Pair.create(applicationInfo.splitNames[i],
|
||||
new File(applicationInfo.splitSourceDirs[i])));
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0, size = filesToChecksum.size(); i < size; ++i) {
|
||||
final File file = filesToChecksum.get(i).second;
|
||||
if (!file.exists()) {
|
||||
throw new IllegalStateException("File not found: " + file.getPath());
|
||||
}
|
||||
}
|
||||
|
||||
final Certificate[] trustedCerts = (trustedInstallers != null) ? decodeCertificates(
|
||||
trustedInstallers) : null;
|
||||
final Context context = mContext;
|
||||
|
||||
mInjector.getBackgroundExecutor().execute(() -> {
|
||||
final Intent intent = new Intent();
|
||||
List<FileChecksum> result = 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;
|
||||
try {
|
||||
result.addAll(ApkChecksums.getFileChecksums(split, file, optional, required,
|
||||
trustedCerts));
|
||||
} catch (Throwable e) {
|
||||
Slog.e(TAG, "Checksum calculation error", e);
|
||||
}
|
||||
}
|
||||
intent.putExtra(EXTRA_CHECKSUMS,
|
||||
result.toArray(new FileChecksum[result.size()]));
|
||||
|
||||
try {
|
||||
statusReceiver.sendIntent(context, 1, intent, null, null);
|
||||
} catch (SendIntentException e) {
|
||||
Slog.w(TAG, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static @NonNull Certificate[] decodeCertificates(@NonNull List certs) {
|
||||
try {
|
||||
final CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||
final Certificate[] result = new Certificate[certs.size()];
|
||||
for (int i = 0, size = certs.size(); i < size; ++i) {
|
||||
final InputStream is = new ByteArrayInputStream((byte[]) certs.get(i));
|
||||
final X509Certificate cert = (X509Certificate) cf.generateCertificate(is);
|
||||
result[i] = cert;
|
||||
}
|
||||
return result;
|
||||
} catch (CertificateException e) {
|
||||
throw ExceptionUtils.propagate(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the type of the external storage a package is installed on.
|
||||
* @param packageVolume The storage volume of the package.
|
||||
|
||||
@@ -52,6 +52,9 @@ abstract public class VerityUtils {
|
||||
/** The maximum size of signature file. This is just to avoid potential abuse. */
|
||||
private static final int MAX_SIGNATURE_FILE_SIZE_BYTES = 8192;
|
||||
|
||||
/** SHA256 hash size. */
|
||||
private static final int HASH_SIZE_BYTES = 32;
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
/** Returns true if the given file looks like containing an fs-verity signature. */
|
||||
@@ -90,8 +93,23 @@ abstract public class VerityUtils {
|
||||
return (retval == 1);
|
||||
}
|
||||
|
||||
/** Returns hash of a root node for the fs-verity enabled file. */
|
||||
public static byte[] getFsverityRootHash(@NonNull String filePath) {
|
||||
byte[] result = new byte[HASH_SIZE_BYTES];
|
||||
int retval = measureFsverityNative(filePath, result);
|
||||
if (retval < 0) {
|
||||
if (retval != -OsConstants.ENODATA) {
|
||||
Slog.e(TAG, "Failed to measure fs-verity, errno " + -retval + ": " + filePath);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static native int enableFsverityNative(@NonNull String filePath,
|
||||
@NonNull byte[] pkcs7Signature);
|
||||
private static native int measureFsverityNative(@NonNull String filePath,
|
||||
@NonNull byte[] digest);
|
||||
private static native int statxForFsverityNative(@NonNull String filePath);
|
||||
|
||||
/**
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
|
||||
#include <android-base/unique_fd.h>
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
namespace android {
|
||||
|
||||
namespace {
|
||||
@@ -53,7 +55,7 @@ int enableFsverity(JNIEnv* env, jobject /* clazz */, jstring filePath, jbyteArra
|
||||
|
||||
fsverity_enable_arg arg = {};
|
||||
arg.version = 1;
|
||||
arg.hash_algorithm = FS_VERITY_HASH_ALG_SHA256;
|
||||
arg.hash_algorithm = FS_VERITY_HASH_ALG_SHA256; // hardcoded in measureFsverity below
|
||||
arg.block_size = 4096;
|
||||
arg.salt_size = 0;
|
||||
arg.salt_ptr = reinterpret_cast<uintptr_t>(nullptr);
|
||||
@@ -85,9 +87,41 @@ int statxForFsverity(JNIEnv *env, jobject /* clazz */, jstring filePath) {
|
||||
return (out.stx_attributes & STATX_ATTR_VERITY) != 0;
|
||||
}
|
||||
|
||||
int measureFsverity(JNIEnv *env, jobject /* clazz */, jstring filePath, jbyteArray digest) {
|
||||
static constexpr auto kDigestSha256 = 32;
|
||||
using Storage = std::aligned_storage_t<sizeof(fsverity_digest) + kDigestSha256>;
|
||||
|
||||
Storage bytes;
|
||||
fsverity_digest *data = reinterpret_cast<fsverity_digest *>(&bytes);
|
||||
data->digest_size = kDigestSha256; // the only input/output parameter
|
||||
|
||||
ScopedUtfChars path(env, filePath);
|
||||
::android::base::unique_fd rfd(open(path.c_str(), O_RDONLY | O_CLOEXEC));
|
||||
if (rfd.get() < 0) {
|
||||
return rfd.get();
|
||||
}
|
||||
if (auto err = ioctl(rfd.get(), FS_IOC_MEASURE_VERITY, data); err < 0) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (data->digest_algorithm != FS_VERITY_HASH_ALG_SHA256) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (digest != nullptr && data->digest_size > 0) {
|
||||
auto digestSize = env->GetArrayLength(digest);
|
||||
if (data->digest_size > digestSize) {
|
||||
return -E2BIG;
|
||||
}
|
||||
env->SetByteArrayRegion(digest, 0, data->digest_size, (const jbyte *)data->digest);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
const JNINativeMethod sMethods[] = {
|
||||
{"enableFsverityNative", "(Ljava/lang/String;[B)I", (void *)enableFsverity},
|
||||
{"statxForFsverityNative", "(Ljava/lang/String;)I", (void *)statxForFsverity},
|
||||
{"measureFsverityNative", "(Ljava/lang/String;[B)I", (void *)measureFsverity},
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
Reference in New Issue
Block a user