Merge "APK digest API and initial implementation."

This commit is contained in:
Alex Buynytskyy
2020-08-25 15:38:23 +00:00
committed by Android (Google) Code Review
17 changed files with 1076 additions and 61 deletions

View File

@@ -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 {

View File

@@ -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

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 FileChecksum;

View 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
}

View File

@@ -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

View File

@@ -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.
*

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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;
}
}
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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,

View File

@@ -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 {

View 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);
}
}
}

View File

@@ -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.

View File

@@ -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);
/**

View File

@@ -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