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