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  */
16 package com.android.server.pm;
17 
18 import android.annotation.NonNull;
19 import android.annotation.UserIdInt;
20 import android.content.pm.PackageInfo;
21 import android.content.pm.PackageManagerInternal;
22 import android.content.pm.ShortcutInfo;
23 import android.content.pm.Signature;
24 import android.content.pm.SigningInfo;
25 import android.util.Slog;
26 
27 import com.android.internal.annotations.VisibleForTesting;
28 import com.android.server.LocalServices;
29 import com.android.server.backup.BackupUtils;
30 
31 import libcore.util.HexEncoding;
32 
33 import org.xmlpull.v1.XmlPullParser;
34 import org.xmlpull.v1.XmlPullParserException;
35 import org.xmlpull.v1.XmlSerializer;
36 
37 import java.io.IOException;
38 import java.io.PrintWriter;
39 import java.util.ArrayList;
40 import java.util.Base64;
41 
42 /**
43  * Package information used by {@link android.content.pm.ShortcutManager} for backup / restore.
44  *
45  * All methods should be guarded by {@code ShortcutService.mLock}.
46  */
47 class ShortcutPackageInfo {
48     private static final String TAG = ShortcutService.TAG;
49 
50     static final String TAG_ROOT = "package-info";
51     private static final String ATTR_VERSION = "version";
52     private static final String ATTR_LAST_UPDATE_TIME = "last_udpate_time";
53     private static final String ATTR_BACKUP_SOURCE_VERSION = "bk_src_version";
54     private static final String ATTR_BACKUP_ALLOWED = "allow-backup";
55     private static final String ATTR_BACKUP_ALLOWED_INITIALIZED = "allow-backup-initialized";
56     private static final String ATTR_BACKUP_SOURCE_BACKUP_ALLOWED = "bk_src_backup-allowed";
57     private static final String ATTR_SHADOW = "shadow";
58 
59     private static final String TAG_SIGNATURE = "signature";
60     private static final String ATTR_SIGNATURE_HASH = "hash";
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 long mVersionCode = ShortcutInfo.VERSION_CODE_UNKNOWN;
68     private long mBackupSourceVersionCode = ShortcutInfo.VERSION_CODE_UNKNOWN;
69     private long mLastUpdateTime;
70     private ArrayList<byte[]> mSigHashes;
71 
72     // mBackupAllowed didn't used to be parsisted, so we don't restore it from a file.
73     // mBackupAllowed will always start with false, and will have been updated before making a
74     // backup next time, which works file.
75     // We just don't want to print an uninitialzied mBackupAlldowed value on dumpsys, so
76     // we use this boolean to control dumpsys.
77     private boolean mBackupAllowedInitialized;
78     private boolean mBackupAllowed;
79     private boolean mBackupSourceBackupAllowed;
80 
ShortcutPackageInfo(long versionCode, long lastUpdateTime, ArrayList<byte[]> sigHashes, boolean isShadow)81     private ShortcutPackageInfo(long versionCode, long lastUpdateTime,
82             ArrayList<byte[]> sigHashes, boolean isShadow) {
83         mVersionCode = versionCode;
84         mLastUpdateTime = lastUpdateTime;
85         mIsShadow = isShadow;
86         mSigHashes = sigHashes;
87         mBackupAllowed = false; // By default, we assume false.
88         mBackupSourceBackupAllowed = false;
89     }
90 
newEmpty()91     public static ShortcutPackageInfo newEmpty() {
92         return new ShortcutPackageInfo(ShortcutInfo.VERSION_CODE_UNKNOWN, /* last update time =*/ 0,
93                 new ArrayList<>(0), /* isShadow */ false);
94     }
95 
isShadow()96     public boolean isShadow() {
97         return mIsShadow;
98     }
99 
setShadow(boolean shadow)100     public void setShadow(boolean shadow) {
101         mIsShadow = shadow;
102     }
103 
getVersionCode()104     public long getVersionCode() {
105         return mVersionCode;
106     }
107 
getBackupSourceVersionCode()108     public long getBackupSourceVersionCode() {
109         return mBackupSourceVersionCode;
110     }
111 
112     @VisibleForTesting
isBackupSourceBackupAllowed()113     public boolean isBackupSourceBackupAllowed() {
114         return mBackupSourceBackupAllowed;
115     }
116 
getLastUpdateTime()117     public long getLastUpdateTime() {
118         return mLastUpdateTime;
119     }
120 
isBackupAllowed()121     public boolean isBackupAllowed() {
122         return mBackupAllowed;
123     }
124 
125     /**
126      * Set {@link #mVersionCode}, {@link #mLastUpdateTime} and {@link #mBackupAllowed}
127      * from a {@link PackageInfo}.
128      */
updateFromPackageInfo(@onNull PackageInfo pi)129     public void updateFromPackageInfo(@NonNull PackageInfo pi) {
130         if (pi != null) {
131             mVersionCode = pi.getLongVersionCode();
132             mLastUpdateTime = pi.lastUpdateTime;
133             mBackupAllowed = ShortcutService.shouldBackupApp(pi);
134             mBackupAllowedInitialized = true;
135         }
136     }
137 
hasSignatures()138     public boolean hasSignatures() {
139         return mSigHashes.size() > 0;
140     }
141 
142     //@DisabledReason
canRestoreTo(ShortcutService s, PackageInfo currentPackage, boolean anyVersionOkay)143     public int canRestoreTo(ShortcutService s, PackageInfo currentPackage, boolean anyVersionOkay) {
144         PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
145         if (!BackupUtils.signaturesMatch(mSigHashes, currentPackage, pmi)) {
146             Slog.w(TAG, "Can't restore: Package signature mismatch");
147             return ShortcutInfo.DISABLED_REASON_SIGNATURE_MISMATCH;
148         }
149         if (!ShortcutService.shouldBackupApp(currentPackage) || !mBackupSourceBackupAllowed) {
150             // "allowBackup" was true when backed up, but now false.
151             Slog.w(TAG, "Can't restore: package didn't or doesn't allow backup");
152             return ShortcutInfo.DISABLED_REASON_BACKUP_NOT_SUPPORTED;
153         }
154         if (!anyVersionOkay && (currentPackage.getLongVersionCode() < mBackupSourceVersionCode)) {
155             Slog.w(TAG, String.format(
156                     "Can't restore: package current version %d < backed up version %d",
157                     currentPackage.getLongVersionCode(), mBackupSourceVersionCode));
158             return ShortcutInfo.DISABLED_REASON_VERSION_LOWER;
159         }
160         return ShortcutInfo.DISABLED_REASON_NOT_DISABLED;
161     }
162 
163     @VisibleForTesting
generateForInstalledPackageForTest( ShortcutService s, String packageName, @UserIdInt int packageUserId)164     public static ShortcutPackageInfo generateForInstalledPackageForTest(
165             ShortcutService s, String packageName, @UserIdInt int packageUserId) {
166         final PackageInfo pi = s.getPackageInfoWithSignatures(packageName, packageUserId);
167         // retrieve the newest sigs
168         SigningInfo signingInfo = pi.signingInfo;
169         if (signingInfo == null) {
170             Slog.e(TAG, "Can't get signatures: package=" + packageName);
171             return null;
172         }
173         // TODO (b/73988180) use entire signing history in case of rollbacks
174         Signature[] signatures = signingInfo.getApkContentsSigners();
175         final ShortcutPackageInfo ret = new ShortcutPackageInfo(pi.getLongVersionCode(),
176                 pi.lastUpdateTime, BackupUtils.hashSignatureArray(signatures), /* shadow=*/ false);
177 
178         ret.mBackupSourceBackupAllowed = s.shouldBackupApp(pi);
179         ret.mBackupSourceVersionCode = pi.getLongVersionCode();
180         return ret;
181     }
182 
refreshSignature(ShortcutService s, ShortcutPackageItem pkg)183     public void refreshSignature(ShortcutService s, ShortcutPackageItem pkg) {
184         if (mIsShadow) {
185             s.wtf("Attempted to refresh package info for shadow package " + pkg.getPackageName()
186                     + ", user=" + pkg.getOwnerUserId());
187             return;
188         }
189         // Note use mUserId here, rather than userId.
190         final PackageInfo pi = s.getPackageInfoWithSignatures(
191                 pkg.getPackageName(), pkg.getPackageUserId());
192         if (pi == null) {
193             Slog.w(TAG, "Package not found: " + pkg.getPackageName());
194             return;
195         }
196         // retrieve the newest sigs
197         SigningInfo signingInfo = pi.signingInfo;
198         if (signingInfo == null) {
199             Slog.w(TAG, "Not refreshing signature for " + pkg.getPackageName()
200                     + " since it appears to have no signing info.");
201             return;
202         }
203         // TODO (b/73988180) use entire signing history in case of rollbacks
204         Signature[] signatures = signingInfo.getApkContentsSigners();
205         mSigHashes = BackupUtils.hashSignatureArray(signatures);
206     }
207 
saveToXml(ShortcutService s, XmlSerializer out, boolean forBackup)208     public void saveToXml(ShortcutService s, XmlSerializer out, boolean forBackup)
209             throws IOException {
210         if (forBackup && !mBackupAllowedInitialized) {
211             s.wtf("Backup happened before mBackupAllowed is initialized.");
212         }
213 
214         out.startTag(null, TAG_ROOT);
215 
216         ShortcutService.writeAttr(out, ATTR_VERSION, mVersionCode);
217         ShortcutService.writeAttr(out, ATTR_LAST_UPDATE_TIME, mLastUpdateTime);
218         ShortcutService.writeAttr(out, ATTR_SHADOW, mIsShadow);
219         ShortcutService.writeAttr(out, ATTR_BACKUP_ALLOWED, mBackupAllowed);
220 
221         // We don't need to save this field (we don't even read it back), but it'll show up
222         // in the dumpsys in the backup / restore payload.
223         ShortcutService.writeAttr(out, ATTR_BACKUP_ALLOWED_INITIALIZED, mBackupAllowedInitialized);
224 
225         ShortcutService.writeAttr(out, ATTR_BACKUP_SOURCE_VERSION, mBackupSourceVersionCode);
226         ShortcutService.writeAttr(out,
227                 ATTR_BACKUP_SOURCE_BACKUP_ALLOWED, mBackupSourceBackupAllowed);
228 
229 
230         for (int i = 0; i < mSigHashes.size(); i++) {
231             out.startTag(null, TAG_SIGNATURE);
232             final String encoded = Base64.getEncoder().encodeToString(mSigHashes.get(i));
233             ShortcutService.writeAttr(out, ATTR_SIGNATURE_HASH, encoded);
234             out.endTag(null, TAG_SIGNATURE);
235         }
236         out.endTag(null, TAG_ROOT);
237     }
238 
loadFromXml(XmlPullParser parser, boolean fromBackup)239     public void loadFromXml(XmlPullParser parser, boolean fromBackup)
240             throws IOException, XmlPullParserException {
241         // Don't use the version code from the backup file.
242         final long versionCode = ShortcutService.parseLongAttribute(parser, ATTR_VERSION,
243                 ShortcutInfo.VERSION_CODE_UNKNOWN);
244 
245         final long lastUpdateTime = ShortcutService.parseLongAttribute(
246                 parser, ATTR_LAST_UPDATE_TIME);
247 
248         // When restoring from backup, it's always shadow.
249         final boolean shadow =
250                 fromBackup || ShortcutService.parseBooleanAttribute(parser, ATTR_SHADOW);
251 
252         // We didn't used to save these attributes, and all backed up shortcuts were from
253         // apps that support backups, so the default values take this fact into consideration.
254         final long backupSourceVersion = ShortcutService.parseLongAttribute(parser,
255                 ATTR_BACKUP_SOURCE_VERSION, ShortcutInfo.VERSION_CODE_UNKNOWN);
256 
257         // Note the only time these "true" default value is used is when restoring from an old
258         // build that didn't save ATTR_BACKUP_ALLOWED, and that means all the data included in
259         // a backup file were from apps that support backup, so we can just use "true" as the
260         // default.
261         final boolean backupAllowed = ShortcutService.parseBooleanAttribute(
262                 parser, ATTR_BACKUP_ALLOWED, true);
263         final boolean backupSourceBackupAllowed = ShortcutService.parseBooleanAttribute(
264                 parser, ATTR_BACKUP_SOURCE_BACKUP_ALLOWED, true);
265 
266         final ArrayList<byte[]> hashes = new ArrayList<>();
267 
268         final int outerDepth = parser.getDepth();
269         int type;
270         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
271                 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
272             if (type != XmlPullParser.START_TAG) {
273                 continue;
274             }
275             final int depth = parser.getDepth();
276             final String tag = parser.getName();
277 
278             if (depth == outerDepth + 1) {
279                 switch (tag) {
280                     case TAG_SIGNATURE: {
281                         final String hash = ShortcutService.parseStringAttribute(
282                                 parser, ATTR_SIGNATURE_HASH);
283                         // Throws IllegalArgumentException if hash is invalid base64 data
284                         final byte[] decoded = Base64.getDecoder().decode(hash);
285                         hashes.add(decoded);
286                         continue;
287                     }
288                 }
289             }
290             ShortcutService.warnForInvalidTag(depth, tag);
291         }
292 
293         // Successfully loaded; replace the fields.
294         if (fromBackup) {
295             mVersionCode = ShortcutInfo.VERSION_CODE_UNKNOWN;
296             mBackupSourceVersionCode = versionCode;
297             mBackupSourceBackupAllowed = backupAllowed;
298         } else {
299             mVersionCode = versionCode;
300             mBackupSourceVersionCode = backupSourceVersion;
301             mBackupSourceBackupAllowed = backupSourceBackupAllowed;
302         }
303         mLastUpdateTime = lastUpdateTime;
304         mIsShadow = shadow;
305         mSigHashes = hashes;
306 
307         // Note we don't restore it from the file because it didn't used to be saved.
308         // We always start by assuming backup is disabled for the current package,
309         // and this field will have been updated before we actually create a backup, at the same
310         // time when we update the version code.
311         // Until then, the value of mBackupAllowed shouldn't matter, but we don't want to print
312         // a false flag on dumpsys, so set mBackupAllowedInitialized to false.
313         mBackupAllowed = false;
314         mBackupAllowedInitialized = false;
315     }
316 
dump(PrintWriter pw, String prefix)317     public void dump(PrintWriter pw, String prefix) {
318         pw.println();
319 
320         pw.print(prefix);
321         pw.println("PackageInfo:");
322 
323         pw.print(prefix);
324         pw.print("  IsShadow: ");
325         pw.print(mIsShadow);
326         pw.print(mIsShadow ? " (not installed)" : " (installed)");
327         pw.println();
328 
329         pw.print(prefix);
330         pw.print("  Version: ");
331         pw.print(mVersionCode);
332         pw.println();
333 
334         if (mBackupAllowedInitialized) {
335             pw.print(prefix);
336             pw.print("  Backup Allowed: ");
337             pw.print(mBackupAllowed);
338             pw.println();
339         }
340 
341         if (mBackupSourceVersionCode != ShortcutInfo.VERSION_CODE_UNKNOWN) {
342             pw.print(prefix);
343             pw.print("  Backup source version: ");
344             pw.print(mBackupSourceVersionCode);
345             pw.println();
346 
347             pw.print(prefix);
348             pw.print("  Backup source backup allowed: ");
349             pw.print(mBackupSourceBackupAllowed);
350             pw.println();
351         }
352 
353         pw.print(prefix);
354         pw.print("  Last package update time: ");
355         pw.print(mLastUpdateTime);
356         pw.println();
357 
358         for (int i = 0; i < mSigHashes.size(); i++) {
359             pw.print(prefix);
360             pw.print("    ");
361             pw.print("SigHash: ");
362             pw.println(HexEncoding.encode(mSigHashes.get(i)));
363         }
364     }
365 }
366