ShortcutPackageInfo.java revision 0acbb14574d859b5f1cc0b7c6bbdfbeba38f3e55
1/* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16package com.android.server.pm; 17 18import android.annotation.NonNull; 19import android.annotation.UserIdInt; 20import android.content.pm.ApplicationInfo; 21import android.content.pm.PackageInfo; 22import android.content.pm.Signature; 23import android.util.Slog; 24 25import com.android.internal.util.Preconditions; 26 27import libcore.io.Base64; 28import libcore.util.HexEncoding; 29 30import org.xmlpull.v1.XmlPullParser; 31import org.xmlpull.v1.XmlPullParserException; 32import org.xmlpull.v1.XmlSerializer; 33 34import java.io.IOException; 35import java.io.PrintWriter; 36import java.security.MessageDigest; 37import java.security.NoSuchAlgorithmException; 38import java.util.ArrayList; 39import java.util.Arrays; 40 41/** 42 * Package information used by {@link android.content.pm.ShortcutManager} for backup / restore. 43 * 44 * TODO: The methods about signature hashes are copied from BackupManagerService, which is not 45 * visible here. Unify the code. 46 */ 47class ShortcutPackageInfo implements ShortcutPackageItem { 48 private static final String TAG = ShortcutService.TAG; 49 50 static final String TAG_ROOT = "package-info"; 51 private static final String ATTR_NAME = "name"; 52 private static final String ATTR_VERSION = "version"; 53 private static final String ATTR_SHADOW = "shadow"; 54 55 private static final String TAG_SIGNATURE = "signature"; 56 private static final String ATTR_SIGNATURE_HASH = "hash"; 57 58 public interface ShortcutPackageInfoHolder { 59 ShortcutPackageInfo getShortcutPackageInfo(); 60 } 61 62 private final String mPackageName; 63 64 /** 65 * When true, this package information was restored from the previous device, and the app hasn't 66 * been installed yet. 67 */ 68 private boolean mIsShadow; 69 private int mVersionCode; 70 private ArrayList<byte[]> mSigHashes; 71 72 private ShortcutPackageInfo(String packageName, int versionCode, ArrayList<byte[]> sigHashes, 73 boolean isShadow) { 74 mVersionCode = versionCode; 75 mIsShadow = isShadow; 76 mSigHashes = sigHashes; 77 mPackageName = Preconditions.checkNotNull(packageName); 78 } 79 80 @NonNull 81 public String getPackageName() { 82 return mPackageName; 83 } 84 85 public boolean isShadow() { 86 return mIsShadow; 87 } 88 89 public boolean isInstalled() { 90 return !mIsShadow; 91 } 92 93 public void setShadow(boolean shadow) { 94 mIsShadow = shadow; 95 } 96 97 public int getVersionCode() { 98 return mVersionCode; 99 } 100 101 private static byte[] hashSignature(Signature sig) { 102 try { 103 MessageDigest digest = MessageDigest.getInstance("SHA-256"); 104 digest.update(sig.toByteArray()); 105 return digest.digest(); 106 } catch (NoSuchAlgorithmException e) { 107 Slog.w(TAG, "No SHA-256 algorithm found!"); 108 } 109 return null; 110 } 111 112 private static ArrayList<byte[]> hashSignatureArray(Signature[] sigs) { 113 if (sigs == null) { 114 return null; 115 } 116 117 ArrayList<byte[]> hashes = new ArrayList<byte[]>(sigs.length); 118 for (Signature s : sigs) { 119 hashes.add(hashSignature(s)); 120 } 121 return hashes; 122 } 123 124 private static boolean signaturesMatch(ArrayList<byte[]> storedSigHashes, PackageInfo target) { 125 if (target == null) { 126 return false; 127 } 128 129 // If the target resides on the system partition, we allow it to restore 130 // data from the like-named package in a restore set even if the signatures 131 // do not match. (Unlike general applications, those flashed to the system 132 // partition will be signed with the device's platform certificate, so on 133 // different phones the same system app will have different signatures.) 134 if ((target.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { 135 return true; 136 } 137 138 // Allow unsigned apps, but not signed on one device and unsigned on the other 139 // !!! TODO: is this the right policy? 140 Signature[] deviceSigs = target.signatures; 141 if ((storedSigHashes == null || storedSigHashes.size() == 0) 142 && (deviceSigs == null || deviceSigs.length == 0)) { 143 return true; 144 } 145 if (storedSigHashes == null || deviceSigs == null) { 146 return false; 147 } 148 149 // !!! TODO: this demands that every stored signature match one 150 // that is present on device, and does not demand the converse. 151 // Is this this right policy? 152 final int nStored = storedSigHashes.size(); 153 final int nDevice = deviceSigs.length; 154 155 // hash each on-device signature 156 ArrayList<byte[]> deviceHashes = new ArrayList<byte[]>(nDevice); 157 for (int i = 0; i < nDevice; i++) { 158 deviceHashes.add(hashSignature(deviceSigs[i])); 159 } 160 161 // now ensure that each stored sig (hash) matches an on-device sig (hash) 162 for (int n = 0; n < nStored; n++) { 163 boolean match = false; 164 final byte[] storedHash = storedSigHashes.get(n); 165 for (int i = 0; i < nDevice; i++) { 166 if (Arrays.equals(storedHash, deviceHashes.get(i))) { 167 match = true; 168 break; 169 } 170 } 171 // match is false when no on-device sig matched one of the stored ones 172 if (!match) { 173 return false; 174 } 175 } 176 177 return true; 178 } 179 180 public boolean canRestoreTo(PackageInfo target) { 181 if (target.versionCode < mVersionCode) { 182 Slog.w(TAG, String.format("Package current version %d < backed up version %d", 183 target.versionCode, mVersionCode)); 184 return false; 185 } 186 if (!signaturesMatch(mSigHashes, target)) { 187 Slog.w(TAG, "Package signature mismtach"); 188 return false; 189 } 190 return true; 191 } 192 193 public static ShortcutPackageInfo generateForInstalledPackage( 194 ShortcutService s, String packageName, @UserIdInt int userId) { 195 final PackageInfo pi = s.getPackageInfo(packageName, userId, /*signature=*/ true); 196 if (pi.signatures == null || pi.signatures.length == 0) { 197 Slog.e(TAG, "Can't get signatures: package=" + packageName); 198 return null; 199 } 200 final ShortcutPackageInfo ret = new ShortcutPackageInfo(packageName, pi.versionCode, 201 hashSignatureArray(pi.signatures), /* shadow=*/ false); 202 203 return ret; 204 } 205 206 public void refreshAndSave(ShortcutService s, @UserIdInt int userId) { 207 final PackageInfo pi = s.getPackageInfo(mPackageName, userId, /*getSignatures=*/ true); 208 if (pi == null) { 209 Slog.w(TAG, "Package not found: " + mPackageName); 210 return; 211 } 212 mVersionCode = pi.versionCode; 213 mSigHashes = hashSignatureArray(pi.signatures); 214 215 s.scheduleSaveUser(userId); 216 } 217 218 public void saveToXml(XmlSerializer out, boolean forBackup) 219 throws IOException, XmlPullParserException { 220 221 out.startTag(null, TAG_ROOT); 222 223 ShortcutService.writeAttr(out, ATTR_NAME, mPackageName); 224 ShortcutService.writeAttr(out, ATTR_VERSION, mVersionCode); 225 ShortcutService.writeAttr(out, ATTR_SHADOW, mIsShadow); 226 227 for (int i = 0; i < mSigHashes.size(); i++) { 228 out.startTag(null, TAG_SIGNATURE); 229 ShortcutService.writeAttr(out, ATTR_SIGNATURE_HASH, Base64.encode(mSigHashes.get(i))); 230 out.endTag(null, TAG_SIGNATURE); 231 } 232 out.endTag(null, TAG_ROOT); 233 } 234 235 public static ShortcutPackageInfo loadFromXml(XmlPullParser parser) 236 throws IOException, XmlPullParserException { 237 238 final String packageName = ShortcutService.parseStringAttribute(parser, ATTR_NAME); 239 final int versionCode = ShortcutService.parseIntAttribute(parser, ATTR_VERSION); 240 final boolean shadow = ShortcutService.parseBooleanAttribute(parser, ATTR_SHADOW); 241 242 final ArrayList<byte[]> hashes = new ArrayList<>(); 243 244 245 final int outerDepth = parser.getDepth(); 246 int type; 247 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 248 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 249 if (type != XmlPullParser.START_TAG) { 250 continue; 251 } 252 final int depth = parser.getDepth(); 253 final String tag = parser.getName(); 254 switch (tag) { 255 case TAG_SIGNATURE: { 256 final String hash = ShortcutService.parseStringAttribute( 257 parser, ATTR_SIGNATURE_HASH); 258 hashes.add(Base64.decode(hash.getBytes())); 259 continue; 260 } 261 } 262 throw ShortcutService.throwForInvalidTag(depth, tag); 263 } 264 return new ShortcutPackageInfo(packageName, versionCode, hashes, shadow); 265 } 266 267 public void dump(ShortcutService s, PrintWriter pw, String prefix) { 268 pw.println(); 269 270 pw.print(prefix); 271 pw.print("PackageInfo: "); 272 pw.print(mPackageName); 273 pw.println(); 274 275 pw.print(prefix); 276 pw.print(" IsShadow: "); 277 pw.print(mIsShadow); 278 pw.println(); 279 280 pw.print(prefix); 281 pw.print(" Version: "); 282 pw.print(mVersionCode); 283 pw.println(); 284 285 for (int i = 0; i < mSigHashes.size(); i++) { 286 pw.print(prefix); 287 pw.print(" "); 288 pw.print("SigHash: "); 289 pw.println(HexEncoding.encode(mSigHashes.get(i))); 290 } 291 } 292} 293