Merge "Use all certs for computing package signing sha256" into oc-mr1-dev

am: ee580764ad

Change-Id: Ie766ecd41d2ac07dd3608fa4ed69edc3c575058f
This commit is contained in:
Svetoslav Ganov
2017-08-23 23:21:18 +00:00
committed by android-build-merger
8 changed files with 266 additions and 66 deletions

View File

@@ -187,8 +187,17 @@ public class PackageInfo implements Parcelable {
public static final int REQUESTED_PERMISSION_GRANTED = 1<<1;
/**
* Array of all signatures read from the package file. This is only filled
* in if the flag {@link PackageManager#GET_SIGNATURES} was set.
* Array of all signatures read from the package file. This is only filled
* in if the flag {@link PackageManager#GET_SIGNATURES} was set. A package
* must be singed with at least one certificate which is at position zero.
* The package can be signed with additional certificates which appear as
* subsequent entries.
*
* <strong>Note:</strong> Signature ordering is not guaranteed to be
* stable which means that a package signed with certificates A and B is
* equivalent to being signed with certificates B and A. This means that
* in case multiple signatures are reported you cannot assume the one at
* the first position to be the same across updates.
*/
public Signature[] signatures;

View File

@@ -97,6 +97,7 @@ import com.android.internal.util.XmlUtils;
import libcore.io.IoUtils;
import libcore.util.EmptyArray;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -2824,14 +2825,14 @@ public class PackageParser {
com.android.internal.R.styleable.AndroidManifestUsesLibrary_name);
final int version = sa.getInt(
com.android.internal.R.styleable.AndroidManifestUsesStaticLibrary_version, -1);
String certSha256 = sa.getNonResourceString(com.android.internal.R.styleable
String certSha256Digest = sa.getNonResourceString(com.android.internal.R.styleable
.AndroidManifestUsesStaticLibrary_certDigest);
sa.recycle();
// Since an APK providing a static shared lib can only provide the lib - fail if malformed
if (lname == null || version < 0 || certSha256 == null) {
if (lname == null || version < 0 || certSha256Digest == null) {
outError[0] = "Bad uses-static-library declaration name: " + lname + " version: "
+ version + " certDigest" + certSha256;
+ version + " certDigest" + certSha256Digest;
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
XmlUtils.skipCurrentTag(parser);
return false;
@@ -2848,18 +2849,75 @@ public class PackageParser {
lname = lname.intern();
// We allow ":" delimiters in the SHA declaration as this is the format
// emitted by the certtool making it easy for developers to copy/paste.
certSha256 = certSha256.replace(":", "").toLowerCase();
certSha256Digest = certSha256Digest.replace(":", "").toLowerCase();
// Fot apps targeting O-MR1 we require explicit enumeration of all certs.
String[] additionalCertSha256Digests = EmptyArray.STRING;
if (pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.O) {
additionalCertSha256Digests = parseAdditionalCertificates(res, parser, outError);
if (additionalCertSha256Digests == null) {
return false;
}
} else {
XmlUtils.skipCurrentTag(parser);
}
final String[] certSha256Digests = new String[additionalCertSha256Digests.length + 1];
certSha256Digests[0] = certSha256Digest;
System.arraycopy(additionalCertSha256Digests, 0, certSha256Digests,
1, additionalCertSha256Digests.length);
pkg.usesStaticLibraries = ArrayUtils.add(pkg.usesStaticLibraries, lname);
pkg.usesStaticLibrariesVersions = ArrayUtils.appendInt(
pkg.usesStaticLibrariesVersions, version, true);
pkg.usesStaticLibrariesCertDigests = ArrayUtils.appendElement(String.class,
pkg.usesStaticLibrariesCertDigests, certSha256, true);
XmlUtils.skipCurrentTag(parser);
pkg.usesStaticLibrariesCertDigests = ArrayUtils.appendElement(String[].class,
pkg.usesStaticLibrariesCertDigests, certSha256Digests, true);
return true;
}
private String[] parseAdditionalCertificates(Resources resources, XmlResourceParser parser,
String[] outError) throws XmlPullParserException, IOException {
String[] certSha256Digests = EmptyArray.STRING;
int outerDepth = parser.getDepth();
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
continue;
}
final String nodeName = parser.getName();
if (nodeName.equals("additional-certificate")) {
final TypedArray sa = resources.obtainAttributes(parser, com.android.internal.
R.styleable.AndroidManifestAdditionalCertificate);
String certSha256Digest = sa.getNonResourceString(com.android.internal.
R.styleable.AndroidManifestAdditionalCertificate_certDigest);
sa.recycle();
if (TextUtils.isEmpty(certSha256Digest)) {
outError[0] = "Bad additional-certificate declaration with empty"
+ " certDigest:" + certSha256Digest;
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
XmlUtils.skipCurrentTag(parser);
sa.recycle();
return null;
}
// We allow ":" delimiters in the SHA declaration as this is the format
// emitted by the certtool making it easy for developers to copy/paste.
certSha256Digest = certSha256Digest.replace(":", "").toLowerCase();
certSha256Digests = ArrayUtils.appendElement(String.class,
certSha256Digests, certSha256Digest);
} else {
XmlUtils.skipCurrentTag(parser);
}
}
return certSha256Digests;
}
private boolean parseUsesPermission(Package pkg, Resources res, XmlResourceParser parser)
throws XmlPullParserException, IOException {
TypedArray sa = res.obtainAttributes(parser,
@@ -5820,7 +5878,7 @@ public class PackageParser {
public ArrayList<String> usesLibraries = null;
public ArrayList<String> usesStaticLibraries = null;
public int[] usesStaticLibrariesVersions = null;
public String[] usesStaticLibrariesCertDigests = null;
public String[][] usesStaticLibrariesCertDigests = null;
public ArrayList<String> usesOptionalLibraries = null;
public String[] usesLibraryFiles = null;
@@ -6318,8 +6376,10 @@ public class PackageParser {
internStringArrayList(usesStaticLibraries);
usesStaticLibrariesVersions = new int[libCount];
dest.readIntArray(usesStaticLibrariesVersions);
usesStaticLibrariesCertDigests = new String[libCount];
dest.readStringArray(usesStaticLibrariesCertDigests);
usesStaticLibrariesCertDigests = new String[libCount][];
for (int i = 0; i < libCount; i++) {
usesStaticLibrariesCertDigests[i] = dest.createStringArray();
}
}
preferredActivityFilters = new ArrayList<>();
@@ -6465,7 +6525,9 @@ public class PackageParser {
dest.writeInt(usesStaticLibraries.size());
dest.writeStringList(usesStaticLibraries);
dest.writeIntArray(usesStaticLibrariesVersions);
dest.writeStringArray(usesStaticLibrariesCertDigests);
for (String[] usesStaticLibrariesCertDigest : usesStaticLibrariesCertDigests) {
dest.writeStringArray(usesStaticLibrariesCertDigest);
}
}
dest.writeParcelableList(preferredActivityFilters, flags);

View File

@@ -18,12 +18,13 @@ package android.util;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
/**
* Helper functions applicable to packages.
@@ -36,32 +37,67 @@ public final class PackageUtils {
}
/**
* Computes the SHA256 digest of the signing cert for a package.
* @param packageManager The package manager.
* @param packageName The package for which to generate the digest.
* @param userId The user for which to generate the digest.
* @return The digest or null if the package does not exist for this user.
* Computes the SHA256 digests of a list of signatures. Items in the
* resulting array of hashes correspond to the signatures in the
* input array.
* @param signatures The signatures.
* @return The digest array.
*/
public static @Nullable String computePackageCertSha256Digest(
@NonNull PackageManager packageManager,
@NonNull String packageName, int userId) {
final PackageInfo packageInfo;
try {
packageInfo = packageManager.getPackageInfoAsUser(packageName,
PackageManager.GET_SIGNATURES, userId);
} catch (PackageManager.NameNotFoundException e) {
return null;
public static @NonNull String[] computeSignaturesSha256Digests(
@NonNull Signature[] signatures) {
final int signatureCount = signatures.length;
final String[] digests = new String[signatureCount];
for (int i = 0; i < signatureCount; i++) {
digests[i] = computeSha256Digest(signatures[i].toByteArray());
}
return computeCertSha256Digest(packageInfo.signatures[0]);
return digests;
}
/**
* Computes a SHA256 digest of the signatures' SHA256 digests. First,
* individual hashes for each signature is derived in a hexademical
* form, then these strings are sorted based the natural ordering, and
* finally a hash is derived from these strings' bytes.
* @param signatures The signatures.
* @return The digest.
*/
public static @NonNull String computeSignaturesSha256Digest(
@NonNull Signature[] signatures) {
// Shortcut for optimization - most apps singed by a single cert
if (signatures.length == 1) {
return computeSha256Digest(signatures[0].toByteArray());
}
// Make sure these are sorted to handle reversed certificates
final String[] sha256Digests = computeSignaturesSha256Digests(signatures);
return computeSignaturesSha256Digest(sha256Digests);
}
/**
* Computes the SHA256 digest of a cert.
* @param signature The signature.
* @return The digest or null if an error occurs.
* Computes a SHA256 digest in of the signatures SHA256 digests. First,
* the strings are sorted based the natural ordering, and then a hash is
* derived from these strings' bytes.
* @param sha256Digests Signature SHA256 hashes in hexademical form.
* @return The digest.
*/
public static @Nullable String computeCertSha256Digest(@NonNull Signature signature) {
return computeSha256Digest(signature.toByteArray());
public static @NonNull String computeSignaturesSha256Digest(
@NonNull String[] sha256Digests) {
// Shortcut for optimization - most apps singed by a single cert
if (sha256Digests.length == 1) {
return sha256Digests[0];
}
// Make sure these are sorted to handle reversed certificates
Arrays.sort(sha256Digests);
final ByteArrayOutputStream bytes = new ByteArrayOutputStream();
for (String sha256Digest : sha256Digests) {
try {
bytes.write(sha256Digest.getBytes());
} catch (IOException e) {
/* ignore - can't happen */
}
}
return computeSha256Digest(bytes.toByteArray());
}
/**

View File

@@ -1779,6 +1779,11 @@
the library at build time while it offers apps to share code defined in such
libraries. Hence, static libraries are strictly required.
<p>On devices running O MR1 or higher, if the library is singed with multiple
signing certificates you must to specify the SHA-256 hashes of the additional
certificates via adding
{@link #AndroidManifestAdditionalCertificate additional-certificate} tags.
<p>This appears as a child tag of the
{@link #AndroidManifestApplication application} tag. -->
<declare-styleable name="AndroidManifestUsesStaticLibrary" parent="AndroidManifestApplication">
@@ -1790,6 +1795,17 @@
<attr name="certDigest" format="string" />
</declare-styleable>
<!-- The <code>additional-certificate</code> specifies the SHA-256 digest of a static
shared library's additional signing certificate. You need to use this tag if the
library is singed with more than one certificate.
<p>This appears as a child tag of the
{@link #AndroidManifestUsesStaticLibrary uses-static-library} tag. -->
<declare-styleable name="AndroidManifestAdditionalCertificate" parent="AndroidManifestUsesStaticLibrary">
<!-- The SHA-256 digest of the library signing certificate. -->
<attr name="certDigest" />
</declare-styleable>
<!-- The <code>supports-screens</code> specifies the screen dimensions an
application supports. By default a modern application supports all
screen sizes and must explicitly disable certain screen sizes here;

View File

@@ -23,6 +23,7 @@ import android.annotation.IntRange;
import android.annotation.NonNull;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.UserHandle;
@@ -30,6 +31,7 @@ import android.text.TextUtils;
import android.util.Log;
import android.util.PackageUtils;
import android.util.Pair;
import android.util.Slog;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.PackageMonitor;
@@ -126,9 +128,18 @@ public final class AccountManagerBackupHelper {
} catch (PackageManager.NameNotFoundException e) {
return false;
}
String currentCertDigest = PackageUtils.computeCertSha256Digest(
packageInfo.signatures[0]);
if (!certDigest.equals(currentCertDigest)) {
// Before we used only the first signature to compute the SHA 256 but some
// apps could be singed by multiple certs and the cert order is undefined.
// We prefer the modern computation procedure where all certs are taken
// into account but also allow the value from the old computation to allow
// restoring backed up grants on an older platform version.
final String[] signaturesSha256Digests = PackageUtils.computeSignaturesSha256Digests(
packageInfo.signatures);
final String signaturesSha256Digest = PackageUtils.computeSignaturesSha256Digest(
signaturesSha256Digests);
if (!certDigest.equals(signaturesSha256Digest) && (packageInfo.signatures.length <= 1
|| !certDigest.equals(signaturesSha256Digests[0]))) {
return false;
}
final int uid = packageInfo.applicationInfo.uid;
@@ -169,8 +180,17 @@ public final class AccountManagerBackupHelper {
}
for (String packageName : packageNames) {
String digest = PackageUtils.computePackageCertSha256Digest(
packageManager, packageName, userId);
final PackageInfo packageInfo;
try {
packageInfo = packageManager.getPackageInfoAsUser(packageName,
PackageManager.GET_SIGNATURES, userId);
} catch (PackageManager.NameNotFoundException e) {
Slog.i(TAG, "Skipping backup of account access grant for"
+ " non-existing package: " + packageName);
continue;
}
final String digest = PackageUtils.computeSignaturesSha256Digest(
packageInfo.signatures);
if (digest != null) {
serializer.startTag(null, TAG_PERMISSION);
serializer.attribute(null, ATTR_ACCOUNT_SHA_256,

View File

@@ -293,14 +293,35 @@ class InstantAppRegistry {
if (currentCookieFile == null) {
continue;
}
File expectedCookeFile = computeInstantCookieFile(pkg, userId);
if (!currentCookieFile.equals(expectedCookeFile)) {
Slog.i(LOG_TAG, "Signature for package " + pkg.packageName
+ " changed - dropping cookie");
// Make sure a pending write for the old signed app is cancelled
mCookiePersistence.cancelPendingPersistLPw(pkg, userId);
currentCookieFile.delete();
// Before we used only the first signature to compute the SHA 256 but some
// apps could be singed by multiple certs and the cert order is undefined.
// We prefer the modern computation procedure where all certs are taken
// into account but also allow the value from the old computation to avoid
// data loss.
final String[] signaturesSha256Digests = PackageUtils.computeSignaturesSha256Digests(
pkg.mSignatures);
final String signaturesSha256Digest = PackageUtils.computeSignaturesSha256Digest(
signaturesSha256Digests);
// We prefer a match based on all signatures
if (currentCookieFile.equals(computeInstantCookieFile(pkg.packageName,
signaturesSha256Digest, userId))) {
return;
}
// For backwards compatibility we accept match based on first signature
if (pkg.mSignatures.length > 1 && currentCookieFile.equals(computeInstantCookieFile(
pkg.packageName, signaturesSha256Digests[0], userId))) {
return;
}
// Sorry, you are out of luck - different signatures - nuke data
Slog.i(LOG_TAG, "Signature for package " + pkg.packageName
+ " changed - dropping cookie");
// Make sure a pending write for the old signed app is cancelled
mCookiePersistence.cancelPendingPersistLPw(pkg, userId);
currentCookieFile.delete();
}
}
@@ -968,11 +989,11 @@ class InstantAppRegistry {
}
}
private static @NonNull File computeInstantCookieFile(@NonNull PackageParser.Package pkg,
@UserIdInt int userId) {
File appDir = getInstantApplicationDir(pkg.packageName, userId);
String cookieFile = INSTANT_APP_COOKIE_FILE_PREFIX + PackageUtils.computeSha256Digest(
pkg.mSignatures[0].toByteArray()) + INSTANT_APP_COOKIE_FILE_SIFFIX;
private static @NonNull File computeInstantCookieFile(@NonNull String packageName,
@NonNull String sha256Digest, @UserIdInt int userId) {
final File appDir = getInstantApplicationDir(packageName, userId);
final String cookieFile = INSTANT_APP_COOKIE_FILE_PREFIX
+ sha256Digest + INSTANT_APP_COOKIE_FILE_SIFFIX;
return new File(appDir, cookieFile);
}
@@ -1147,9 +1168,20 @@ class InstantAppRegistry {
public void schedulePersistLPw(@UserIdInt int userId, @NonNull PackageParser.Package pkg,
@NonNull byte[] cookie) {
File cookieFile = computeInstantCookieFile(pkg, userId);
// Before we used only the first signature to compute the SHA 256 but some
// apps could be singed by multiple certs and the cert order is undefined.
// We prefer the modern computation procedure where all certs are taken
// into account and delete the file derived via the legacy hash computation.
File newCookieFile = computeInstantCookieFile(pkg.packageName,
PackageUtils.computeSignaturesSha256Digest(pkg.mSignatures), userId);
if (pkg.mSignatures.length > 0) {
File oldCookieFile = peekInstantCookieFile(pkg.packageName, userId);
if (oldCookieFile != null && !newCookieFile.equals(oldCookieFile)) {
oldCookieFile.delete();
}
}
cancelPendingPersistLPw(pkg, userId);
addPendingPersistCookieLPw(userId, pkg, cookie, cookieFile);
addPendingPersistCookieLPw(userId, pkg, cookie, newCookieFile);
sendMessageDelayed(obtainMessage(userId, pkg),
PERSIST_COOKIE_DELAY_MILLIS);
}

View File

@@ -10385,16 +10385,19 @@ public class PackageManagerService extends IPackageManager.Stub
ArraySet<String> usesLibraryFiles = null;
if (pkg.usesLibraries != null) {
usesLibraryFiles = addSharedLibrariesLPw(pkg.usesLibraries,
null, null, pkg.packageName, changingLib, true, null);
null, null, pkg.packageName, changingLib, true,
pkg.applicationInfo.targetSdkVersion, null);
}
if (pkg.usesStaticLibraries != null) {
usesLibraryFiles = addSharedLibrariesLPw(pkg.usesStaticLibraries,
pkg.usesStaticLibrariesVersions, pkg.usesStaticLibrariesCertDigests,
pkg.packageName, changingLib, true, usesLibraryFiles);
pkg.packageName, changingLib, true,
pkg.applicationInfo.targetSdkVersion, usesLibraryFiles);
}
if (pkg.usesOptionalLibraries != null) {
usesLibraryFiles = addSharedLibrariesLPw(pkg.usesOptionalLibraries,
null, null, pkg.packageName, changingLib, false, usesLibraryFiles);
null, null, pkg.packageName, changingLib, false,
pkg.applicationInfo.targetSdkVersion, usesLibraryFiles);
}
if (!ArrayUtils.isEmpty(usesLibraryFiles)) {
pkg.usesLibraryFiles = usesLibraryFiles.toArray(new String[usesLibraryFiles.size()]);
@@ -10404,9 +10407,9 @@ public class PackageManagerService extends IPackageManager.Stub
}
private ArraySet<String> addSharedLibrariesLPw(@NonNull List<String> requestedLibraries,
@Nullable int[] requiredVersions, @Nullable String[] requiredCertDigests,
@Nullable int[] requiredVersions, @Nullable String[][] requiredCertDigests,
@NonNull String packageName, @Nullable PackageParser.Package changingLib,
boolean required, @Nullable ArraySet<String> outUsedLibraries)
boolean required, int targetSdk, @Nullable ArraySet<String> outUsedLibraries)
throws PackageManagerException {
final int libCount = requestedLibraries.size();
for (int i = 0; i < libCount; i++) {
@@ -10440,13 +10443,34 @@ public class PackageManagerService extends IPackageManager.Stub
+ " library; failing!");
}
String expectedCertDigest = requiredCertDigests[i];
String libCertDigest = PackageUtils.computeCertSha256Digest(
libPkg.mSignatures[0]);
if (!libCertDigest.equalsIgnoreCase(expectedCertDigest)) {
final String[] expectedCertDigests = requiredCertDigests[i];
// For apps targeting O MR1 we require explicit enumeration of all certs.
final String[] libCertDigests = (targetSdk > Build.VERSION_CODES.O)
? PackageUtils.computeSignaturesSha256Digests(libPkg.mSignatures)
: PackageUtils.computeSignaturesSha256Digests(
new Signature[]{libPkg.mSignatures[0]});
// Take a shortcut if sizes don't match. Note that if an app doesn't
// target O we don't parse the "additional-certificate" tags similarly
// how we only consider all certs only for apps targeting O (see above).
// Therefore, the size check is safe to make.
if (expectedCertDigests.length != libCertDigests.length) {
throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
"Package " + packageName + " requires differently signed" +
" static shared library; failing!");
" static sDexLoadReporter.java:45.19hared library; failing!");
}
// Use a predictable order as signature order may vary
Arrays.sort(libCertDigests);
Arrays.sort(expectedCertDigests);
final int certCount = libCertDigests.length;
for (int j = 0; j < certCount; j++) {
if (!libCertDigests[j].equalsIgnoreCase(expectedCertDigests[j])) {
throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
"Package " + packageName + " requires differently signed" +
" static shared library; failing!");
}
}
}

View File

@@ -467,7 +467,8 @@ public class PackageParserTest {
pkg.staticSharedLibVersion = 100;
pkg.usesStaticLibraries = new ArrayList<>();
pkg.usesStaticLibraries.add("foo23");
pkg.usesStaticLibrariesCertDigests = new String[] { "digest" };
pkg.usesStaticLibrariesCertDigests = new String[1][];
pkg.usesStaticLibrariesCertDigests[0] = new String[] { "digest" };
pkg.usesStaticLibrariesVersions = new int[] { 100 };
pkg.libraryNames = new ArrayList<>();