ShortcutPackageInfo.java revision d99c6f04bbb68f8be78f2c3ca625a3a8d5645275
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_USER_ID = "user"; 52 private static final String ATTR_NAME = "name"; 53 private static final String ATTR_VERSION = "version"; 54 private static final String ATTR_SHADOW = "shadow"; 55 56 private static final String TAG_SIGNATURE = "signature"; 57 private static final String ATTR_SIGNATURE_HASH = "hash"; 58 59 private final String mPackageName; 60 private final int mUserId; 61 62 /** 63 * When true, this package information was restored from the previous device, and the app hasn't 64 * been installed yet. 65 */ 66 private boolean mIsShadow; 67 private int mVersionCode; 68 private ArrayList<byte[]> mSigHashes; 69 70 private ShortcutPackageInfo(String packageName, int userId, 71 int versionCode, ArrayList<byte[]> sigHashes, boolean isShadow) { 72 mPackageName = Preconditions.checkNotNull(packageName); 73 mUserId = userId; 74 mVersionCode = versionCode; 75 mIsShadow = isShadow; 76 mSigHashes = sigHashes; 77 } 78 79 @NonNull 80 public String getPackageName() { 81 return mPackageName; 82 } 83 84 public int getUserId() { 85 return mUserId; 86 } 87 88 public boolean isShadow() { 89 return mIsShadow; 90 } 91 92 public boolean isInstalled() { 93 return !mIsShadow; 94 } 95 96 public void setShadow(boolean shadow) { 97 mIsShadow = shadow; 98 } 99 100 public int getVersionCode() { 101 return mVersionCode; 102 } 103 104 private static byte[] hashSignature(Signature sig) { 105 try { 106 MessageDigest digest = MessageDigest.getInstance("SHA-256"); 107 digest.update(sig.toByteArray()); 108 return digest.digest(); 109 } catch (NoSuchAlgorithmException e) { 110 Slog.w(TAG, "No SHA-256 algorithm found!"); 111 } 112 return null; 113 } 114 115 private static ArrayList<byte[]> hashSignatureArray(Signature[] sigs) { 116 if (sigs == null) { 117 return null; 118 } 119 120 ArrayList<byte[]> hashes = new ArrayList<byte[]>(sigs.length); 121 for (Signature s : sigs) { 122 hashes.add(hashSignature(s)); 123 } 124 return hashes; 125 } 126 127 private static boolean signaturesMatch(ArrayList<byte[]> storedSigHashes, PackageInfo target) { 128 if (target == null) { 129 return false; 130 } 131 132 // If the target resides on the system partition, we allow it to restore 133 // data from the like-named package in a restore set even if the signatures 134 // do not match. (Unlike general applications, those flashed to the system 135 // partition will be signed with the device's platform certificate, so on 136 // different phones the same system app will have different signatures.) 137 if ((target.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { 138 return true; 139 } 140 141 // Allow unsigned apps, but not signed on one device and unsigned on the other 142 // !!! TODO: is this the right policy? 143 Signature[] deviceSigs = target.signatures; 144 if ((storedSigHashes == null || storedSigHashes.size() == 0) 145 && (deviceSigs == null || deviceSigs.length == 0)) { 146 return true; 147 } 148 if (storedSigHashes == null || deviceSigs == null) { 149 return false; 150 } 151 152 // !!! TODO: this demands that every stored signature match one 153 // that is present on device, and does not demand the converse. 154 // Is this this right policy? 155 final int nStored = storedSigHashes.size(); 156 final int nDevice = deviceSigs.length; 157 158 // hash each on-device signature 159 ArrayList<byte[]> deviceHashes = new ArrayList<byte[]>(nDevice); 160 for (int i = 0; i < nDevice; i++) { 161 deviceHashes.add(hashSignature(deviceSigs[i])); 162 } 163 164 // now ensure that each stored sig (hash) matches an on-device sig (hash) 165 for (int n = 0; n < nStored; n++) { 166 boolean match = false; 167 final byte[] storedHash = storedSigHashes.get(n); 168 for (int i = 0; i < nDevice; i++) { 169 if (Arrays.equals(storedHash, deviceHashes.get(i))) { 170 match = true; 171 break; 172 } 173 } 174 // match is false when no on-device sig matched one of the stored ones 175 if (!match) { 176 return false; 177 } 178 } 179 180 return true; 181 } 182 183 public boolean canRestoreTo(PackageInfo target) { 184 if (target.versionCode < mVersionCode) { 185 Slog.w(TAG, String.format("Package current version %d < backed up version %d", 186 target.versionCode, mVersionCode)); 187 return false; 188 } 189 if (!signaturesMatch(mSigHashes, target)) { 190 Slog.w(TAG, "Package signature mismtach"); 191 return false; 192 } 193 return true; 194 } 195 196 public static ShortcutPackageInfo generateForInstalledPackage( 197 ShortcutService s, String packageName, @UserIdInt int userId) { 198 final PackageInfo pi = s.getPackageInfoWithSignatures(packageName, userId); 199 if (pi.signatures == null || pi.signatures.length == 0) { 200 Slog.e(TAG, "Can't get signatures: package=" + packageName); 201 return null; 202 } 203 final ShortcutPackageInfo ret = new ShortcutPackageInfo(packageName, userId, pi.versionCode, 204 hashSignatureArray(pi.signatures), /* shadow=*/ false); 205 206 return ret; 207 } 208 209 public void refreshAndSave(ShortcutService s, @UserIdInt int userId) { 210 final PackageInfo pi = s.getPackageInfoWithSignatures(mPackageName, userId); 211 if (pi == null) { 212 Slog.w(TAG, "Package not found: " + mPackageName); 213 return; 214 } 215 mVersionCode = pi.versionCode; 216 mSigHashes = hashSignatureArray(pi.signatures); 217 218 s.scheduleSaveUser(userId); 219 } 220 221 public void saveToXml(XmlSerializer out, boolean forBackup) 222 throws IOException, XmlPullParserException { 223 224 out.startTag(null, TAG_ROOT); 225 226 ShortcutService.writeAttr(out, ATTR_NAME, mPackageName); 227 ShortcutService.writeAttr(out, ATTR_USER_ID, mUserId); 228 ShortcutService.writeAttr(out, ATTR_VERSION, mVersionCode); 229 ShortcutService.writeAttr(out, ATTR_SHADOW, mIsShadow); 230 231 for (int i = 0; i < mSigHashes.size(); i++) { 232 out.startTag(null, TAG_SIGNATURE); 233 ShortcutService.writeAttr(out, ATTR_SIGNATURE_HASH, Base64.encode(mSigHashes.get(i))); 234 out.endTag(null, TAG_SIGNATURE); 235 } 236 out.endTag(null, TAG_ROOT); 237 } 238 239 public static ShortcutPackageInfo loadFromXml(XmlPullParser parser, int ownerUserId) 240 throws IOException, XmlPullParserException { 241 242 final String packageName = ShortcutService.parseStringAttribute(parser, ATTR_NAME); 243 final int userId = ShortcutService.parseIntAttribute(parser, ATTR_USER_ID, ownerUserId); 244 final int versionCode = ShortcutService.parseIntAttribute(parser, ATTR_VERSION); 245 final boolean shadow = ShortcutService.parseBooleanAttribute(parser, ATTR_SHADOW); 246 247 final ArrayList<byte[]> hashes = new ArrayList<>(); 248 249 250 final int outerDepth = parser.getDepth(); 251 int type; 252 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 253 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 254 if (type != XmlPullParser.START_TAG) { 255 continue; 256 } 257 final int depth = parser.getDepth(); 258 final String tag = parser.getName(); 259 switch (tag) { 260 case TAG_SIGNATURE: { 261 final String hash = ShortcutService.parseStringAttribute( 262 parser, ATTR_SIGNATURE_HASH); 263 hashes.add(Base64.decode(hash.getBytes())); 264 continue; 265 } 266 } 267 throw ShortcutService.throwForInvalidTag(depth, tag); 268 } 269 return new ShortcutPackageInfo(packageName, userId, versionCode, hashes, shadow); 270 } 271 272 public void dump(ShortcutService s, PrintWriter pw, String prefix) { 273 pw.println(); 274 275 pw.print(prefix); 276 pw.print("PackageInfo: "); 277 pw.print(mPackageName); 278 pw.println(); 279 280 pw.print(prefix); 281 pw.print(" User: "); 282 pw.print(mUserId); 283 pw.println(); 284 285 pw.print(prefix); 286 pw.print(" IsShadow: "); 287 pw.print(mIsShadow); 288 pw.println(); 289 290 pw.print(prefix); 291 pw.print(" Version: "); 292 pw.print(mVersionCode); 293 pw.println(); 294 295 for (int i = 0; i < mSigHashes.size(); i++) { 296 pw.print(prefix); 297 pw.print(" "); 298 pw.print("SigHash: "); 299 pw.println(HexEncoding.encode(mSigHashes.get(i))); 300 } 301 } 302} 303