blob: 4a8e31835dd8a5320327304f888c3af7a3061498 [file] [log] [blame]
/*
* Copyright (C) 2013 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 android.content.pm.PackageParser;
import android.os.Binder;
import android.util.ArraySet;
import android.util.Base64;
import android.util.Slog;
import android.util.LongSparseArray;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.PublicKey;
import java.util.Map;
import java.util.Set;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
/*
* Manages system-wide KeySet state.
*/
public class KeySetManagerService {
static final String TAG = "KeySetManagerService";
/* original keysets implementation had no versioning info, so this is the first */
public static final int FIRST_VERSION = 1;
public static final int CURRENT_VERSION = FIRST_VERSION;
/** Sentinel value returned when a {@code KeySet} is not found. */
public static final long KEYSET_NOT_FOUND = -1;
/** Sentinel value returned when public key is not found. */
protected static final long PUBLIC_KEY_NOT_FOUND = -1;
private final LongSparseArray<KeySetHandle> mKeySets;
private final LongSparseArray<PublicKey> mPublicKeys;
protected final LongSparseArray<ArraySet<Long>> mKeySetMapping;
private final Map<String, PackageSetting> mPackages;
private static long lastIssuedKeySetId = 0;
private static long lastIssuedKeyId = 0;
public KeySetManagerService(Map<String, PackageSetting> packages) {
mKeySets = new LongSparseArray<KeySetHandle>();
mPublicKeys = new LongSparseArray<PublicKey>();
mKeySetMapping = new LongSparseArray<ArraySet<Long>>();
mPackages = packages;
}
/**
* Determine if a package is signed by the given KeySet.
*
* Returns false if the package was not signed by all the
* keys in the KeySet.
*
* Returns true if the package was signed by at least the
* keys in the given KeySet.
*
* Note that this can return true for multiple KeySets.
*/
public boolean packageIsSignedByLPr(String packageName, KeySetHandle ks) {
PackageSetting pkg = mPackages.get(packageName);
if (pkg == null) {
throw new NullPointerException("Invalid package name");
}
if (pkg.keySetData == null) {
throw new NullPointerException("Package has no KeySet data");
}
long id = getIdByKeySetLPr(ks);
if (id == KEYSET_NOT_FOUND) {
return false;
}
return pkg.keySetData.packageIsSignedBy(id);
}
/**
* Determine if a package is signed by the given KeySet.
*
* Returns false if the package was not signed by all the
* keys in the KeySet, or if the package was signed by keys
* not in the KeySet.
*
* Note that this can return only for one KeySet.
*/
public boolean packageIsSignedByExactlyLPr(String packageName, KeySetHandle ks) {
PackageSetting pkg = mPackages.get(packageName);
if (pkg == null) {
throw new NullPointerException("Invalid package name");
}
if (pkg.keySetData == null
|| pkg.keySetData.getProperSigningKeySet()
== PackageKeySetData.KEYSET_UNASSIGNED) {
throw new NullPointerException("Package has no KeySet data");
}
long id = getIdByKeySetLPr(ks);
return pkg.keySetData.getProperSigningKeySet() == id;
}
/**
* This informs the system that the given package has defined a KeySet
* in its manifest that a) contains the given keys and b) is named
* alias by that package.
*/
public void addDefinedKeySetToPackageLPw(String packageName,
ArraySet<PublicKey> keys, String alias) {
if ((packageName == null) || (keys == null) || (alias == null)) {
Slog.w(TAG, "Got null argument for a defined keyset, ignoring!");
return;
}
PackageSetting pkg = mPackages.get(packageName);
if (pkg == null) {
throw new NullPointerException("Unknown package");
}
// Add to KeySets, then to package
KeySetHandle ks = addKeySetLPw(keys);
long id = getIdByKeySetLPr(ks);
pkg.keySetData.addDefinedKeySet(id, alias);
}
/**
* This informs the system that the given package has defined a KeySet
* alias in its manifest to be an upgradeKeySet. This must be called
* after all of the defined KeySets have been added.
*/
public void addUpgradeKeySetToPackageLPw(String packageName, String alias) {
if ((packageName == null) || (alias == null)) {
Slog.w(TAG, "Got null argument for a defined keyset, ignoring!");
return;
}
PackageSetting pkg = mPackages.get(packageName);
if (pkg == null) {
throw new NullPointerException("Unknown package");
}
pkg.keySetData.addUpgradeKeySet(alias);
}
/**
* Similar to the above, this informs the system that the given package
* was signed by the provided KeySet.
*/
public void addSigningKeySetToPackageLPw(String packageName,
ArraySet<PublicKey> signingKeys) {
if ((packageName == null) || (signingKeys == null)) {
Slog.w(TAG, "Got null argument for a signing keyset, ignoring!");
return;
}
// add the signing KeySet
KeySetHandle ks = addKeySetLPw(signingKeys);
long id = getIdByKeySetLPr(ks);
ArraySet<Long> publicKeyIds = mKeySetMapping.get(id);
if (publicKeyIds == null) {
throw new NullPointerException("Got invalid KeySet id");
}
// attach it to the package
PackageSetting pkg = mPackages.get(packageName);
if (pkg == null) {
throw new NullPointerException("No such package!");
}
pkg.keySetData.setProperSigningKeySet(id);
// for each KeySet which is a subset of the one above, add the
// KeySet id to the package's signing KeySets
for (int keySetIndex = 0; keySetIndex < mKeySets.size(); keySetIndex++) {
long keySetID = mKeySets.keyAt(keySetIndex);
ArraySet<Long> definedKeys = mKeySetMapping.get(keySetID);
if (publicKeyIds.containsAll(definedKeys)) {
pkg.keySetData.addSigningKeySet(keySetID);
}
}
}
/**
* Fetches the stable identifier associated with the given KeySet. Returns
* {@link #KEYSET_NOT_FOUND} if the KeySet... wasn't found.
*/
private long getIdByKeySetLPr(KeySetHandle ks) {
for (int keySetIndex = 0; keySetIndex < mKeySets.size(); keySetIndex++) {
KeySetHandle value = mKeySets.valueAt(keySetIndex);
if (ks.equals(value)) {
return mKeySets.keyAt(keySetIndex);
}
}
return KEYSET_NOT_FOUND;
}
/**
* Fetches the KeySet corresponding to the given stable identifier.
*
* Returns {@link #KEYSET_NOT_FOUND} if the identifier doesn't
* identify a {@link KeySet}.
*/
public KeySetHandle getKeySetByIdLPr(long id) {
return mKeySets.get(id);
}
/**
* Fetches the {@link KeySetHandle} that a given package refers to by the
* provided alias. Returns null if the package is unknown or does not have a
* KeySet corresponding to that alias.
*/
public KeySetHandle getKeySetByAliasAndPackageNameLPr(String packageName, String alias) {
PackageSetting p = mPackages.get(packageName);
if (p == null || p.keySetData == null) {
return null;
}
Long keySetId = p.keySetData.getAliases().get(alias);
if (keySetId == null) {
throw new IllegalArgumentException("Unknown KeySet alias: " + alias);
}
return mKeySets.get(keySetId);
}
/**
* Fetches the {@link PublicKey public keys} which belong to the specified
* KeySet id.
*
* Returns {@code null} if the identifier doesn't
* identify a {@link KeySetHandle}.
*/
public ArraySet<PublicKey> getPublicKeysFromKeySetLPr(long id) {
if(mKeySetMapping.get(id) == null) {
return null;
}
ArraySet<PublicKey> mPubKeys = new ArraySet<PublicKey>();
for (long pkId : mKeySetMapping.get(id)) {
mPubKeys.add(mPublicKeys.get(pkId));
}
return mPubKeys;
}
/**
* Fetches the proper {@link KeySetHandle KeySet} that signed the given
* package.
*
* @throws IllegalArgumentException if the package has no keyset data.
* @throws NullPointerException if the package is unknown.
*/
public KeySetHandle getSigningKeySetByPackageNameLPr(String packageName) {
PackageSetting p = mPackages.get(packageName);
if (p == null
|| p.keySetData == null
|| p.keySetData.getProperSigningKeySet()
== PackageKeySetData.KEYSET_UNASSIGNED) {
return null;
}
return mKeySets.get(p.keySetData.getProperSigningKeySet());
}
/**
* Fetches all the known {@link KeySetHandle KeySets} that may upgrade the given
* package.
*
* @throws IllegalArgumentException if the package has no keyset data.
* @throws NullPointerException if the package is unknown.
*/
public ArraySet<KeySetHandle> getUpgradeKeySetsByPackageNameLPr(String packageName) {
ArraySet<KeySetHandle> upgradeKeySets = new ArraySet<KeySetHandle>();
PackageSetting p = mPackages.get(packageName);
if (p == null) {
throw new NullPointerException("Unknown package");
}
if (p.keySetData == null) {
throw new IllegalArgumentException("Package has no keySet data");
}
if (p.keySetData.isUsingUpgradeKeySets()) {
for (long l : p.keySetData.getUpgradeKeySets()) {
upgradeKeySets.add(mKeySets.get(l));
}
}
return upgradeKeySets;
}
/**
* Creates a new KeySet corresponding to the given keys.
*
* If the {@link PublicKey PublicKeys} aren't known to the system, this
* adds them. Otherwise, they're deduped.
*
* If the KeySet isn't known to the system, this adds that and creates the
* mapping to the PublicKeys. If it is known, then it's deduped.
*
* If the KeySet isn't known to the system, this adds it to all appropriate
* signingKeySets
*
* Throws if the provided set is {@code null}.
*/
private KeySetHandle addKeySetLPw(ArraySet<PublicKey> keys) {
if (keys == null) {
throw new NullPointerException("Provided keys cannot be null");
}
// add each of the keys in the provided set
ArraySet<Long> addedKeyIds = new ArraySet<Long>(keys.size());
for (PublicKey k : keys) {
long id = addPublicKeyLPw(k);
addedKeyIds.add(id);
}
// check to see if the resulting keyset is new
long existingKeySetId = getIdFromKeyIdsLPr(addedKeyIds);
if (existingKeySetId != KEYSET_NOT_FOUND) {
return mKeySets.get(existingKeySetId);
}
// create the KeySet object
KeySetHandle ks = new KeySetHandle();
// get the first unoccupied slot in mKeySets
long id = getFreeKeySetIDLPw();
// add the KeySet object to it
mKeySets.put(id, ks);
// add the stable key ids to the mapping
mKeySetMapping.put(id, addedKeyIds);
// add this KeySet id to all packages which are signed by it
for (String pkgName : mPackages.keySet()) {
PackageSetting p = mPackages.get(pkgName);
if (p.keySetData != null) {
long pProperSigning = p.keySetData.getProperSigningKeySet();
if (pProperSigning != PackageKeySetData.KEYSET_UNASSIGNED) {
ArraySet<Long> pSigningKeys = mKeySetMapping.get(pProperSigning);
if (pSigningKeys.containsAll(addedKeyIds)) {
p.keySetData.addSigningKeySet(id);
}
}
}
}
// go home
return ks;
}
/**
* Adds the given PublicKey to the system, deduping as it goes.
*/
private long addPublicKeyLPw(PublicKey key) {
// check if the public key is new
long existingKeyId = getIdForPublicKeyLPr(key);
if (existingKeyId != PUBLIC_KEY_NOT_FOUND) {
return existingKeyId;
}
// if it's new find the first unoccupied slot in the public keys
long id = getFreePublicKeyIdLPw();
// add the public key to it
mPublicKeys.put(id, key);
// return the stable identifier
return id;
}
/**
* Finds the stable identifier for a KeySet based on a set of PublicKey stable IDs.
*
* Returns KEYSET_NOT_FOUND if there isn't one.
*/
private long getIdFromKeyIdsLPr(Set<Long> publicKeyIds) {
for (int keyMapIndex = 0; keyMapIndex < mKeySetMapping.size(); keyMapIndex++) {
ArraySet<Long> value = mKeySetMapping.valueAt(keyMapIndex);
if (value.equals(publicKeyIds)) {
return mKeySetMapping.keyAt(keyMapIndex);
}
}
return KEYSET_NOT_FOUND;
}
/**
* Finds the stable identifier for a PublicKey or PUBLIC_KEY_NOT_FOUND.
*/
private long getIdForPublicKeyLPr(PublicKey k) {
String encodedPublicKey = new String(k.getEncoded());
for (int publicKeyIndex = 0; publicKeyIndex < mPublicKeys.size(); publicKeyIndex++) {
PublicKey value = mPublicKeys.valueAt(publicKeyIndex);
String encodedExistingKey = new String(value.getEncoded());
if (encodedPublicKey.equals(encodedExistingKey)) {
return mPublicKeys.keyAt(publicKeyIndex);
}
}
return PUBLIC_KEY_NOT_FOUND;
}
/**
* Gets an unused stable identifier for a KeySet.
*/
private long getFreeKeySetIDLPw() {
lastIssuedKeySetId += 1;
return lastIssuedKeySetId;
}
/**
* Same as above, but for public keys.
*/
private long getFreePublicKeyIdLPw() {
lastIssuedKeyId += 1;
return lastIssuedKeyId;
}
public void removeAppKeySetDataLPw(String packageName) {
// Get the package's known keys and KeySets
ArraySet<Long> deletableKeySets = getOriginalKeySetsByPackageNameLPr(packageName);
ArraySet<Long> deletableKeys = new ArraySet<Long>();
ArraySet<Long> knownKeys = null;
for (Long ks : deletableKeySets) {
knownKeys = mKeySetMapping.get(ks);
if (knownKeys != null) {
deletableKeys.addAll(knownKeys);
}
}
// Now remove the keys and KeySets on which any other package relies
for (String pkgName : mPackages.keySet()) {
if (pkgName.equals(packageName)) {
continue;
}
ArraySet<Long> knownKeySets = getOriginalKeySetsByPackageNameLPr(pkgName);
deletableKeySets.removeAll(knownKeySets);
knownKeys = new ArraySet<Long>();
for (Long ks : knownKeySets) {
knownKeys = mKeySetMapping.get(ks);
if (knownKeys != null) {
deletableKeys.removeAll(knownKeys);
}
}
}
// The remaining keys and KeySets are not relied on by any other
// application and so can be safely deleted.
for (Long ks : deletableKeySets) {
mKeySets.delete(ks);
mKeySetMapping.delete(ks);
}
for (Long keyId : deletableKeys) {
mPublicKeys.delete(keyId);
}
// Now remove the deleted KeySets from each package's signingKeySets
for (String pkgName : mPackages.keySet()) {
PackageSetting p = mPackages.get(pkgName);
for (Long ks : deletableKeySets) {
p.keySetData.removeSigningKeySet(ks);
}
}
// Finally, remove all KeySets from the original package
PackageSetting p = mPackages.get(packageName);
clearPackageKeySetDataLPw(p);
}
private void clearPackageKeySetDataLPw(PackageSetting p) {
p.keySetData.removeAllSigningKeySets();
p.keySetData.removeAllUpgradeKeySets();
p.keySetData.removeAllDefinedKeySets();
return;
}
private ArraySet<Long> getOriginalKeySetsByPackageNameLPr(String packageName) {
PackageSetting p = mPackages.get(packageName);
if (p == null) {
throw new NullPointerException("Unknown package");
}
if (p.keySetData == null) {
throw new IllegalArgumentException("Package has no keySet data");
}
ArraySet<Long> knownKeySets = new ArraySet<Long>();
knownKeySets.add(p.keySetData.getProperSigningKeySet());
if (p.keySetData.isUsingDefinedKeySets()) {
for (long ks : p.keySetData.getDefinedKeySets()) {
knownKeySets.add(ks);
}
}
return knownKeySets;
}
public String encodePublicKey(PublicKey k) throws IOException {
return new String(Base64.encode(k.getEncoded(), 0));
}
public void dumpLPr(PrintWriter pw, String packageName,
PackageManagerService.DumpState dumpState) {
boolean printedHeader = false;
for (Map.Entry<String, PackageSetting> e : mPackages.entrySet()) {
String keySetPackage = e.getKey();
if (packageName != null && !packageName.equals(keySetPackage)) {
continue;
}
if (!printedHeader) {
if (dumpState.onTitlePrinted())
pw.println();
pw.println("Key Set Manager:");
printedHeader = true;
}
PackageSetting pkg = e.getValue();
pw.print(" ["); pw.print(keySetPackage); pw.println("]");
if (pkg.keySetData != null) {
boolean printedLabel = false;
for (Map.Entry<String, Long> entry : pkg.keySetData.getAliases().entrySet()) {
if (!printedLabel) {
pw.print(" KeySets Aliases: ");
printedLabel = true;
} else {
pw.print(", ");
}
pw.print(entry.getKey());
pw.print('=');
pw.print(Long.toString(entry.getValue()));
}
if (printedLabel) {
pw.println("");
}
printedLabel = false;
if (pkg.keySetData.isUsingDefinedKeySets()) {
for (long keySetId : pkg.keySetData.getDefinedKeySets()) {
if (!printedLabel) {
pw.print(" Defined KeySets: ");
printedLabel = true;
} else {
pw.print(", ");
}
pw.print(Long.toString(keySetId));
}
}
if (printedLabel) {
pw.println("");
}
printedLabel = false;
final long[] signingKeySets = pkg.keySetData.getSigningKeySets();
if (signingKeySets != null) {
for (long keySetId : signingKeySets) {
if (!printedLabel) {
pw.print(" Signing KeySets: ");
printedLabel = true;
} else {
pw.print(", ");
}
pw.print(Long.toString(keySetId));
}
}
if (printedLabel) {
pw.println("");
}
printedLabel = false;
if (pkg.keySetData.isUsingUpgradeKeySets()) {
for (long keySetId : pkg.keySetData.getUpgradeKeySets()) {
if (!printedLabel) {
pw.print(" Upgrade KeySets: ");
printedLabel = true;
} else {
pw.print(", ");
}
pw.print(Long.toString(keySetId));
}
}
if (printedLabel) {
pw.println("");
}
}
}
}
void writeKeySetManagerServiceLPr(XmlSerializer serializer) throws IOException {
serializer.startTag(null, "keyset-settings");
serializer.attribute(null, "version", Integer.toString(CURRENT_VERSION));
writePublicKeysLPr(serializer);
writeKeySetsLPr(serializer);
serializer.startTag(null, "lastIssuedKeyId");
serializer.attribute(null, "value", Long.toString(lastIssuedKeyId));
serializer.endTag(null, "lastIssuedKeyId");
serializer.startTag(null, "lastIssuedKeySetId");
serializer.attribute(null, "value", Long.toString(lastIssuedKeySetId));
serializer.endTag(null, "lastIssuedKeySetId");
serializer.endTag(null, "keyset-settings");
}
void writePublicKeysLPr(XmlSerializer serializer) throws IOException {
serializer.startTag(null, "keys");
for (int pKeyIndex = 0; pKeyIndex < mPublicKeys.size(); pKeyIndex++) {
long id = mPublicKeys.keyAt(pKeyIndex);
PublicKey key = mPublicKeys.valueAt(pKeyIndex);
String encodedKey = encodePublicKey(key);
serializer.startTag(null, "public-key");
serializer.attribute(null, "identifier", Long.toString(id));
serializer.attribute(null, "value", encodedKey);
serializer.endTag(null, "public-key");
}
serializer.endTag(null, "keys");
}
void writeKeySetsLPr(XmlSerializer serializer) throws IOException {
serializer.startTag(null, "keysets");
for (int keySetIndex = 0; keySetIndex < mKeySetMapping.size(); keySetIndex++) {
long id = mKeySetMapping.keyAt(keySetIndex);
ArraySet<Long> keys = mKeySetMapping.valueAt(keySetIndex);
serializer.startTag(null, "keyset");
serializer.attribute(null, "identifier", Long.toString(id));
for (long keyId : keys) {
serializer.startTag(null, "key-id");
serializer.attribute(null, "identifier", Long.toString(keyId));
serializer.endTag(null, "key-id");
}
serializer.endTag(null, "keyset");
}
serializer.endTag(null, "keysets");
}
void readKeySetsLPw(XmlPullParser parser)
throws XmlPullParserException, IOException {
int type;
long currentKeySetId = 0;
int outerDepth = parser.getDepth();
String recordedVersion = parser.getAttributeValue(null, "version");
if (recordedVersion == null || Integer.parseInt(recordedVersion) != CURRENT_VERSION) {
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
// Our version is different than the one which generated the old keyset data.
// We don't want any of the old data, but we must advance the parser
continue;
}
// The KeySet information read previously from packages.xml is invalid.
// Destroy it all.
for (PackageSetting p : mPackages.values()) {
clearPackageKeySetDataLPw(p);
}
return;
}
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 tagName = parser.getName();
if (tagName.equals("keys")) {
readKeysLPw(parser);
} else if (tagName.equals("keysets")) {
readKeySetListLPw(parser);
} else if (tagName.equals("lastIssuedKeyId")) {
lastIssuedKeyId = Long.parseLong(parser.getAttributeValue(null, "value"));
} else if (tagName.equals("lastIssuedKeySetId")) {
lastIssuedKeySetId = Long.parseLong(parser.getAttributeValue(null, "value"));
}
}
}
void readKeysLPw(XmlPullParser parser)
throws XmlPullParserException, IOException {
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 tagName = parser.getName();
if (tagName.equals("public-key")) {
readPublicKeyLPw(parser);
}
}
}
void readKeySetListLPw(XmlPullParser parser)
throws XmlPullParserException, IOException {
int outerDepth = parser.getDepth();
int type;
long currentKeySetId = 0;
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 tagName = parser.getName();
if (tagName.equals("keyset")) {
currentKeySetId = readIdentifierLPw(parser);
mKeySets.put(currentKeySetId, new KeySetHandle());
mKeySetMapping.put(currentKeySetId, new ArraySet<Long>());
} else if (tagName.equals("key-id")) {
long id = readIdentifierLPw(parser);
mKeySetMapping.get(currentKeySetId).add(id);
}
}
}
long readIdentifierLPw(XmlPullParser parser)
throws XmlPullParserException {
return Long.parseLong(parser.getAttributeValue(null, "identifier"));
}
void readPublicKeyLPw(XmlPullParser parser)
throws XmlPullParserException {
String encodedID = parser.getAttributeValue(null, "identifier");
long identifier = Long.parseLong(encodedID);
String encodedPublicKey = parser.getAttributeValue(null, "value");
PublicKey pub = PackageParser.parsePublicKey(encodedPublicKey);
if (pub != null) {
mPublicKeys.put(identifier, pub);
}
}
}