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.util.Slog;
22 
23 import com.android.internal.annotations.VisibleForTesting;
24 import com.android.server.backup.BackupUtils;
25 
26 import libcore.util.HexEncoding;
27 
28 import org.xmlpull.v1.XmlPullParser;
29 import org.xmlpull.v1.XmlPullParserException;
30 import org.xmlpull.v1.XmlSerializer;
31 
32 import java.io.IOException;
33 import java.io.PrintWriter;
34 import java.util.ArrayList;
35 import java.util.Base64;
36 
37 /**
38  * Package information used by {@link android.content.pm.ShortcutManager} for backup / restore.
39  *
40  * All methods should be guarded by {@code ShortcutService.mLock}.
41  */
42 class ShortcutPackageInfo {
43     private static final String TAG = ShortcutService.TAG;
44 
45     static final String TAG_ROOT = "package-info";
46     private static final String ATTR_VERSION = "version";
47     private static final String ATTR_LAST_UPDATE_TIME = "last_udpate_time";
48     private static final String ATTR_SHADOW = "shadow";
49 
50     private static final String TAG_SIGNATURE = "signature";
51     private static final String ATTR_SIGNATURE_HASH = "hash";
52 
53     private static final int VERSION_UNKNOWN = -1;
54 
55     /**
56      * When true, this package information was restored from the previous device, and the app hasn't
57      * been installed yet.
58      */
59     private boolean mIsShadow;
60     private int mVersionCode = VERSION_UNKNOWN;
61     private long mLastUpdateTime;
62     private ArrayList<byte[]> mSigHashes;
63 
ShortcutPackageInfo(int versionCode, long lastUpdateTime, ArrayList<byte[]> sigHashes, boolean isShadow)64     private ShortcutPackageInfo(int versionCode, long lastUpdateTime,
65             ArrayList<byte[]> sigHashes, boolean isShadow) {
66         mVersionCode = versionCode;
67         mLastUpdateTime = lastUpdateTime;
68         mIsShadow = isShadow;
69         mSigHashes = sigHashes;
70     }
71 
newEmpty()72     public static ShortcutPackageInfo newEmpty() {
73         return new ShortcutPackageInfo(VERSION_UNKNOWN, /* last update time =*/ 0,
74                 new ArrayList<>(0), /* isShadow */ false);
75     }
76 
isShadow()77     public boolean isShadow() {
78         return mIsShadow;
79     }
80 
setShadow(boolean shadow)81     public void setShadow(boolean shadow) {
82         mIsShadow = shadow;
83     }
84 
getVersionCode()85     public int getVersionCode() {
86         return mVersionCode;
87     }
88 
getLastUpdateTime()89     public long getLastUpdateTime() {
90         return mLastUpdateTime;
91     }
92 
93     /** Set {@link #mVersionCode} and {@link #mLastUpdateTime} from a {@link PackageInfo}. */
updateVersionInfo(@onNull PackageInfo pi)94     public void updateVersionInfo(@NonNull PackageInfo pi) {
95         if (pi != null) {
96             mVersionCode = pi.versionCode;
97             mLastUpdateTime = pi.lastUpdateTime;
98         }
99     }
100 
hasSignatures()101     public boolean hasSignatures() {
102         return mSigHashes.size() > 0;
103     }
104 
canRestoreTo(ShortcutService s, PackageInfo target)105     public boolean canRestoreTo(ShortcutService s, PackageInfo target) {
106         if (!s.shouldBackupApp(target)) {
107             // "allowBackup" was true when backed up, but now false.
108             Slog.w(TAG, "Can't restore: package no longer allows backup");
109             return false;
110         }
111         if (target.versionCode < mVersionCode) {
112             Slog.w(TAG, String.format(
113                     "Can't restore: package current version %d < backed up version %d",
114                     target.versionCode, mVersionCode));
115             return false;
116         }
117         if (!BackupUtils.signaturesMatch(mSigHashes, target)) {
118             Slog.w(TAG, "Can't restore: Package signature mismatch");
119             return false;
120         }
121         return true;
122     }
123 
124     @VisibleForTesting
generateForInstalledPackageForTest( ShortcutService s, String packageName, @UserIdInt int packageUserId)125     public static ShortcutPackageInfo generateForInstalledPackageForTest(
126             ShortcutService s, String packageName, @UserIdInt int packageUserId) {
127         final PackageInfo pi = s.getPackageInfoWithSignatures(packageName, packageUserId);
128         if (pi.signatures == null || pi.signatures.length == 0) {
129             Slog.e(TAG, "Can't get signatures: package=" + packageName);
130             return null;
131         }
132         final ShortcutPackageInfo ret = new ShortcutPackageInfo(pi.versionCode, pi.lastUpdateTime,
133                 BackupUtils.hashSignatureArray(pi.signatures), /* shadow=*/ false);
134 
135         return ret;
136     }
137 
refreshSignature(ShortcutService s, ShortcutPackageItem pkg)138     public void refreshSignature(ShortcutService s, ShortcutPackageItem pkg) {
139         if (mIsShadow) {
140             s.wtf("Attempted to refresh package info for shadow package " + pkg.getPackageName()
141                     + ", user=" + pkg.getOwnerUserId());
142             return;
143         }
144         // Note use mUserId here, rather than userId.
145         final PackageInfo pi = s.getPackageInfoWithSignatures(
146                 pkg.getPackageName(), pkg.getPackageUserId());
147         if (pi == null) {
148             Slog.w(TAG, "Package not found: " + pkg.getPackageName());
149             return;
150         }
151         mSigHashes = BackupUtils.hashSignatureArray(pi.signatures);
152     }
153 
saveToXml(XmlSerializer out)154     public void saveToXml(XmlSerializer out) throws IOException {
155 
156         out.startTag(null, TAG_ROOT);
157 
158         ShortcutService.writeAttr(out, ATTR_VERSION, mVersionCode);
159         ShortcutService.writeAttr(out, ATTR_LAST_UPDATE_TIME, mLastUpdateTime);
160         ShortcutService.writeAttr(out, ATTR_SHADOW, mIsShadow);
161 
162         for (int i = 0; i < mSigHashes.size(); i++) {
163             out.startTag(null, TAG_SIGNATURE);
164             final String encoded = Base64.getEncoder().encodeToString(mSigHashes.get(i));
165             ShortcutService.writeAttr(out, ATTR_SIGNATURE_HASH, encoded);
166             out.endTag(null, TAG_SIGNATURE);
167         }
168         out.endTag(null, TAG_ROOT);
169     }
170 
loadFromXml(XmlPullParser parser, boolean fromBackup)171     public void loadFromXml(XmlPullParser parser, boolean fromBackup)
172             throws IOException, XmlPullParserException {
173 
174         final int versionCode = ShortcutService.parseIntAttribute(parser, ATTR_VERSION);
175 
176         final long lastUpdateTime = ShortcutService.parseLongAttribute(
177                 parser, ATTR_LAST_UPDATE_TIME);
178 
179         // When restoring from backup, it's always shadow.
180         final boolean shadow =
181                 fromBackup || ShortcutService.parseBooleanAttribute(parser, ATTR_SHADOW);
182 
183         final ArrayList<byte[]> hashes = new ArrayList<>();
184 
185         final int outerDepth = parser.getDepth();
186         int type;
187         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
188                 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
189             if (type != XmlPullParser.START_TAG) {
190                 continue;
191             }
192             final int depth = parser.getDepth();
193             final String tag = parser.getName();
194 
195             if (depth == outerDepth + 1) {
196                 switch (tag) {
197                     case TAG_SIGNATURE: {
198                         final String hash = ShortcutService.parseStringAttribute(
199                                 parser, ATTR_SIGNATURE_HASH);
200                         // Throws IllegalArgumentException if hash is invalid base64 data
201                         final byte[] decoded = Base64.getDecoder().decode(hash);
202                         hashes.add(decoded);
203                         continue;
204                     }
205                 }
206             }
207             ShortcutService.warnForInvalidTag(depth, tag);
208         }
209 
210         // Successfully loaded; replace the feilds.
211         mVersionCode = versionCode;
212         mLastUpdateTime = lastUpdateTime;
213         mIsShadow = shadow;
214         mSigHashes = hashes;
215     }
216 
dump(PrintWriter pw, String prefix)217     public void dump(PrintWriter pw, String prefix) {
218         pw.println();
219 
220         pw.print(prefix);
221         pw.println("PackageInfo:");
222 
223         pw.print(prefix);
224         pw.print("  IsShadow: ");
225         pw.print(mIsShadow);
226         pw.println();
227 
228         pw.print(prefix);
229         pw.print("  Version: ");
230         pw.print(mVersionCode);
231         pw.println();
232 
233         pw.print(prefix);
234         pw.print("  Last package update time: ");
235         pw.print(mLastUpdateTime);
236         pw.println();
237 
238         for (int i = 0; i < mSigHashes.size(); i++) {
239             pw.print(prefix);
240             pw.print("    ");
241             pw.print("SigHash: ");
242             pw.println(HexEncoding.encode(mSigHashes.get(i)));
243         }
244     }
245 }
246