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