1/*
2 * Copyright (C) 2011 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
17package com.android.server.pm;
18
19import com.android.internal.util.XmlUtils;
20
21import org.xmlpull.v1.XmlPullParser;
22import org.xmlpull.v1.XmlPullParserException;
23import org.xmlpull.v1.XmlSerializer;
24
25import android.annotation.NonNull;
26import android.content.pm.PackageParser;
27import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
28import android.content.pm.Signature;
29import android.util.Log;
30
31import java.io.IOException;
32import java.security.cert.CertificateException;
33import java.util.ArrayList;
34
35class PackageSignatures {
36
37    @NonNull PackageParser.SigningDetails mSigningDetails;
38
39    PackageSignatures(PackageSignatures orig) {
40        if (orig != null && orig.mSigningDetails != PackageParser.SigningDetails.UNKNOWN) {
41            mSigningDetails = new PackageParser.SigningDetails(orig.mSigningDetails);
42        } else {
43            mSigningDetails = PackageParser.SigningDetails.UNKNOWN;
44        }
45    }
46
47    PackageSignatures(PackageParser.SigningDetails signingDetails) {
48        mSigningDetails = signingDetails;
49    }
50
51    PackageSignatures() {
52        mSigningDetails = PackageParser.SigningDetails.UNKNOWN;
53    }
54
55    void writeXml(XmlSerializer serializer, String tagName,
56            ArrayList<Signature> writtenSignatures) throws IOException {
57        if (mSigningDetails.signatures == null) {
58            return;
59        }
60        serializer.startTag(null, tagName);
61        serializer.attribute(null, "count", Integer.toString(mSigningDetails.signatures.length));
62        serializer.attribute(null, "schemeVersion",
63                Integer.toString(mSigningDetails.signatureSchemeVersion));
64        writeCertsListXml(serializer, writtenSignatures, mSigningDetails.signatures, null);
65
66        // if we have past signer certificate information, write it out
67        if (mSigningDetails.pastSigningCertificates != null) {
68            serializer.startTag(null, "pastSigs");
69            serializer.attribute(null, "count",
70                    Integer.toString(mSigningDetails.pastSigningCertificates.length));
71            writeCertsListXml(
72                    serializer, writtenSignatures, mSigningDetails.pastSigningCertificates,
73                    mSigningDetails.pastSigningCertificatesFlags);
74            serializer.endTag(null, "pastSigs");
75        }
76        serializer.endTag(null, tagName);
77    }
78
79    private void writeCertsListXml(XmlSerializer serializer, ArrayList<Signature> writtenSignatures,
80            Signature[] signatures, int[] flags) throws IOException {
81        for (int i=0; i<signatures.length; i++) {
82            serializer.startTag(null, "cert");
83            final Signature sig = signatures[i];
84            final int sigHash = sig.hashCode();
85            final int numWritten = writtenSignatures.size();
86            int j;
87            for (j=0; j<numWritten; j++) {
88                Signature writtenSig = writtenSignatures.get(j);
89                if (writtenSig.hashCode() == sigHash && writtenSig.equals(sig)) {
90                    serializer.attribute(null, "index", Integer.toString(j));
91                    break;
92                }
93            }
94            if (j >= numWritten) {
95                writtenSignatures.add(sig);
96                serializer.attribute(null, "index", Integer.toString(numWritten));
97                serializer.attribute(null, "key", sig.toCharsString());
98            }
99            if (flags != null) {
100                serializer.attribute(null, "flags", Integer.toString(flags[i]));
101            }
102            serializer.endTag(null, "cert");
103        }
104    }
105
106    void readXml(XmlPullParser parser, ArrayList<Signature> readSignatures)
107            throws IOException, XmlPullParserException {
108        PackageParser.SigningDetails.Builder builder =
109                new PackageParser.SigningDetails.Builder();
110
111        String countStr = parser.getAttributeValue(null, "count");
112        if (countStr == null) {
113            PackageManagerService.reportSettingsProblem(Log.WARN,
114                    "Error in package manager settings: <sigs> has"
115                       + " no count at " + parser.getPositionDescription());
116            XmlUtils.skipCurrentTag(parser);
117        }
118        final int count = Integer.parseInt(countStr);
119
120        String schemeVersionStr = parser.getAttributeValue(null, "schemeVersion");
121        int signatureSchemeVersion;
122        if (schemeVersionStr == null) {
123            PackageManagerService.reportSettingsProblem(Log.WARN,
124                    "Error in package manager settings: <sigs> has no schemeVersion at "
125                        + parser.getPositionDescription());
126            signatureSchemeVersion = SignatureSchemeVersion.UNKNOWN;
127        } else {
128            signatureSchemeVersion = Integer.parseInt(schemeVersionStr);
129        }
130        builder.setSignatureSchemeVersion(signatureSchemeVersion);
131        Signature[] signatures = new Signature[count];
132        int pos = readCertsListXml(parser, readSignatures, signatures, null, builder);
133        builder.setSignatures(signatures);
134        if (pos < count) {
135            // Should never happen -- there is an error in the written
136            // settings -- but if it does we don't want to generate
137            // a bad array.
138            Signature[] newSigs = new Signature[pos];
139            System.arraycopy(signatures, 0, newSigs, 0, pos);
140            builder = builder.setSignatures(newSigs);
141            PackageManagerService.reportSettingsProblem(Log.WARN,
142                    "Error in package manager settings: <sigs> count does not match number of "
143                            + " <cert> entries" + parser.getPositionDescription());
144        }
145
146        try {
147            mSigningDetails = builder.build();
148        } catch (CertificateException e) {
149            PackageManagerService.reportSettingsProblem(Log.WARN,
150                    "Error in package manager settings: <sigs> "
151                            + "unable to convert certificate(s) to public key(s).");
152            mSigningDetails = PackageParser.SigningDetails.UNKNOWN;
153        }
154    }
155
156    private int readCertsListXml(XmlPullParser parser, ArrayList<Signature> readSignatures,
157            Signature[] signatures, int[] flags, PackageParser.SigningDetails.Builder builder)
158            throws IOException, XmlPullParserException {
159        int count = signatures.length;
160        int pos = 0;
161
162        int outerDepth = parser.getDepth();
163        int type;
164        while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
165                && (type != XmlPullParser.END_TAG
166                || parser.getDepth() > outerDepth)) {
167            if (type == XmlPullParser.END_TAG
168                    || type == XmlPullParser.TEXT) {
169                continue;
170            }
171
172            String tagName = parser.getName();
173            if (tagName.equals("cert")) {
174                if (pos < count) {
175                    String index = parser.getAttributeValue(null, "index");
176                    if (index != null) {
177                        try {
178                            int idx = Integer.parseInt(index);
179                            String key = parser.getAttributeValue(null, "key");
180                            if (key == null) {
181                                if (idx >= 0 && idx < readSignatures.size()) {
182                                    Signature sig = readSignatures.get(idx);
183                                    if (sig != null) {
184                                        signatures[pos] = readSignatures.get(idx);
185                                    } else {
186                                        PackageManagerService.reportSettingsProblem(Log.WARN,
187                                                "Error in package manager settings: <cert> "
188                                                        + "index " + index + " is not defined at "
189                                                        + parser.getPositionDescription());
190                                    }
191                                } else {
192                                    PackageManagerService.reportSettingsProblem(Log.WARN,
193                                            "Error in package manager settings: <cert> "
194                                                    + "index " + index + " is out of bounds at "
195                                                    + parser.getPositionDescription());
196                                }
197                            } else {
198                                while (readSignatures.size() <= idx) {
199                                    readSignatures.add(null);
200                                }
201                                Signature sig = new Signature(key);
202                                readSignatures.set(idx, sig);
203                                signatures[pos] = sig;
204                            }
205                        } catch (NumberFormatException e) {
206                            PackageManagerService.reportSettingsProblem(Log.WARN,
207                                    "Error in package manager settings: <cert> "
208                                            + "index " + index + " is not a number at "
209                                            + parser.getPositionDescription());
210                        } catch (IllegalArgumentException e) {
211                            PackageManagerService.reportSettingsProblem(Log.WARN,
212                                    "Error in package manager settings: <cert> "
213                                            + "index " + index + " has an invalid signature at "
214                                            + parser.getPositionDescription() + ": "
215                                            + e.getMessage());
216                        }
217
218                        if (flags != null) {
219                            String flagsStr = parser.getAttributeValue(null, "flags");
220                            if (flagsStr != null) {
221                                try {
222                                    flags[pos] = Integer.parseInt(flagsStr);
223                                } catch (NumberFormatException e) {
224                                    PackageManagerService.reportSettingsProblem(Log.WARN,
225                                            "Error in package manager settings: <cert> "
226                                                    + "flags " + flagsStr + " is not a number at "
227                                                    + parser.getPositionDescription());
228                                }
229                            } else {
230                                PackageManagerService.reportSettingsProblem(Log.WARN,
231                                        "Error in package manager settings: <cert> has no"
232                                                + " flags at " + parser.getPositionDescription());
233                            }
234                        }
235                    } else {
236                        PackageManagerService.reportSettingsProblem(Log.WARN,
237                                "Error in package manager settings: <cert> has"
238                                        + " no index at " + parser.getPositionDescription());
239                    }
240                } else {
241                    PackageManagerService.reportSettingsProblem(Log.WARN,
242                            "Error in package manager settings: too "
243                                    + "many <cert> tags, expected " + count
244                                    + " at " + parser.getPositionDescription());
245                }
246                pos++;
247                XmlUtils.skipCurrentTag(parser);
248            } else if (tagName.equals("pastSigs")) {
249                if (flags == null) {
250                    // we haven't encountered pastSigs yet, go ahead
251                    String countStr = parser.getAttributeValue(null, "count");
252                    if (countStr == null) {
253                        PackageManagerService.reportSettingsProblem(Log.WARN,
254                                "Error in package manager settings: <pastSigs> has"
255                                        + " no count at " + parser.getPositionDescription());
256                        XmlUtils.skipCurrentTag(parser);
257                    }
258                    try {
259                        final int pastSigsCount = Integer.parseInt(countStr);
260                        Signature[] pastSignatures = new Signature[pastSigsCount];
261                        int[] pastSignaturesFlags = new int[pastSigsCount];
262                        int pastSigsPos = readCertsListXml(parser, readSignatures, pastSignatures,
263                                pastSignaturesFlags, builder);
264                        builder = builder
265                                .setPastSigningCertificates(pastSignatures)
266                                .setPastSigningCertificatesFlags(pastSignaturesFlags);
267
268                        if (pastSigsPos < pastSigsCount) {
269                            // Should never happen -- there is an error in the written
270                            // settings -- but if it does we don't want to generate
271                            // a bad array.
272                            Signature[] newSigs = new Signature[pastSigsPos];
273                            System.arraycopy(pastSignatures, 0, newSigs, 0, pastSigsPos);
274                            int[] newFlags = new int[pastSigsPos];
275                            System.arraycopy(pastSignaturesFlags, 0, newFlags, 0, pastSigsPos);
276                            builder = builder
277                                    .setPastSigningCertificates(newSigs)
278                                    .setPastSigningCertificatesFlags(newFlags);
279                            PackageManagerService.reportSettingsProblem(Log.WARN,
280                                    "Error in package manager settings: <pastSigs> count does not "
281                                    + "match number of <cert> entries "
282                                    + parser.getPositionDescription());
283                        }
284                    } catch (NumberFormatException e) {
285                        PackageManagerService.reportSettingsProblem(Log.WARN,
286                                "Error in package manager settings: <pastSigs> "
287                                        + "count " + countStr + " is not a number at "
288                                        + parser.getPositionDescription());
289                    }
290                } else {
291                    PackageManagerService.reportSettingsProblem(Log.WARN,
292                            "<pastSigs> encountered multiple times under the same <sigs> at "
293                                    + parser.getPositionDescription());
294                    XmlUtils.skipCurrentTag(parser);
295                }
296            } else {
297                PackageManagerService.reportSettingsProblem(Log.WARN,
298                        "Unknown element under <sigs>: "
299                                + parser.getName());
300                XmlUtils.skipCurrentTag(parser);
301            }
302        }
303        return pos;
304    }
305
306    @Override
307    public String toString() {
308        StringBuffer buf = new StringBuffer(128);
309        buf.append("PackageSignatures{");
310        buf.append(Integer.toHexString(System.identityHashCode(this)));
311        buf.append(" version:");
312        buf.append(mSigningDetails.signatureSchemeVersion);
313        buf.append(", signatures:[");
314        if (mSigningDetails.signatures != null) {
315            for (int i = 0; i < mSigningDetails.signatures.length; i++) {
316                if (i > 0) buf.append(", ");
317                buf.append(Integer.toHexString(
318                        mSigningDetails.signatures[i].hashCode()));
319            }
320        }
321        buf.append("]");
322        buf.append(", past signatures:[");
323        if (mSigningDetails.pastSigningCertificates != null) {
324            for (int i = 0; i < mSigningDetails.pastSigningCertificates.length; i++) {
325                if (i > 0) buf.append(", ");
326                buf.append(Integer.toHexString(
327                        mSigningDetails.pastSigningCertificates[i].hashCode()));
328                buf.append(" flags: ");
329                buf.append(Integer.toHexString(mSigningDetails.pastSigningCertificatesFlags[i]));
330            }
331        }
332        buf.append("]}");
333        return buf.toString();
334    }
335}
336