| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.pm; |
| |
| import android.annotation.NonNull; |
| import android.content.pm.PackageParser; |
| import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion; |
| import android.content.pm.Signature; |
| import android.util.Log; |
| |
| import com.android.internal.util.XmlUtils; |
| |
| import org.xmlpull.v1.XmlPullParser; |
| import org.xmlpull.v1.XmlPullParserException; |
| import org.xmlpull.v1.XmlSerializer; |
| |
| import java.io.IOException; |
| import java.security.cert.CertificateException; |
| import java.util.ArrayList; |
| |
| class PackageSignatures { |
| |
| @NonNull PackageParser.SigningDetails mSigningDetails; |
| |
| PackageSignatures(PackageSignatures orig) { |
| if (orig != null && orig.mSigningDetails != PackageParser.SigningDetails.UNKNOWN) { |
| mSigningDetails = new PackageParser.SigningDetails(orig.mSigningDetails); |
| } else { |
| mSigningDetails = PackageParser.SigningDetails.UNKNOWN; |
| } |
| } |
| |
| PackageSignatures(PackageParser.SigningDetails signingDetails) { |
| mSigningDetails = signingDetails; |
| } |
| |
| PackageSignatures() { |
| mSigningDetails = PackageParser.SigningDetails.UNKNOWN; |
| } |
| |
| void writeXml(XmlSerializer serializer, String tagName, |
| ArrayList<Signature> writtenSignatures) throws IOException { |
| if (mSigningDetails.signatures == null) { |
| return; |
| } |
| serializer.startTag(null, tagName); |
| serializer.attribute(null, "count", Integer.toString(mSigningDetails.signatures.length)); |
| serializer.attribute(null, "schemeVersion", |
| Integer.toString(mSigningDetails.signatureSchemeVersion)); |
| writeCertsListXml(serializer, writtenSignatures, mSigningDetails.signatures, false); |
| |
| // if we have past signer certificate information, write it out |
| if (mSigningDetails.pastSigningCertificates != null) { |
| serializer.startTag(null, "pastSigs"); |
| serializer.attribute(null, "count", |
| Integer.toString(mSigningDetails.pastSigningCertificates.length)); |
| writeCertsListXml(serializer, writtenSignatures, |
| mSigningDetails.pastSigningCertificates, true); |
| serializer.endTag(null, "pastSigs"); |
| } |
| serializer.endTag(null, tagName); |
| } |
| |
| private void writeCertsListXml(XmlSerializer serializer, ArrayList<Signature> writtenSignatures, |
| Signature[] signatures, boolean isPastSigs) throws IOException { |
| for (int i=0; i<signatures.length; i++) { |
| serializer.startTag(null, "cert"); |
| final Signature sig = signatures[i]; |
| final int sigHash = sig.hashCode(); |
| final int numWritten = writtenSignatures.size(); |
| int j; |
| for (j=0; j<numWritten; j++) { |
| Signature writtenSig = writtenSignatures.get(j); |
| if (writtenSig.hashCode() == sigHash && writtenSig.equals(sig)) { |
| serializer.attribute(null, "index", Integer.toString(j)); |
| break; |
| } |
| } |
| if (j >= numWritten) { |
| writtenSignatures.add(sig); |
| serializer.attribute(null, "index", Integer.toString(numWritten)); |
| serializer.attribute(null, "key", sig.toCharsString()); |
| } |
| // The flags attribute is only written for previous signatures to represent the |
| // capabilities the developer wants to grant to the previous signing certificates. |
| if (isPastSigs) { |
| serializer.attribute(null, "flags", Integer.toString(sig.getFlags())); |
| } |
| serializer.endTag(null, "cert"); |
| } |
| } |
| |
| void readXml(XmlPullParser parser, ArrayList<Signature> readSignatures) |
| throws IOException, XmlPullParserException { |
| PackageParser.SigningDetails.Builder builder = |
| new PackageParser.SigningDetails.Builder(); |
| |
| String countStr = parser.getAttributeValue(null, "count"); |
| if (countStr == null) { |
| PackageManagerService.reportSettingsProblem(Log.WARN, |
| "Error in package manager settings: <sigs> has" |
| + " no count at " + parser.getPositionDescription()); |
| XmlUtils.skipCurrentTag(parser); |
| return; |
| } |
| final int count = Integer.parseInt(countStr); |
| |
| String schemeVersionStr = parser.getAttributeValue(null, "schemeVersion"); |
| int signatureSchemeVersion; |
| if (schemeVersionStr == null) { |
| PackageManagerService.reportSettingsProblem(Log.WARN, |
| "Error in package manager settings: <sigs> has no schemeVersion at " |
| + parser.getPositionDescription()); |
| signatureSchemeVersion = SignatureSchemeVersion.UNKNOWN; |
| } else { |
| signatureSchemeVersion = Integer.parseInt(schemeVersionStr); |
| } |
| builder.setSignatureSchemeVersion(signatureSchemeVersion); |
| ArrayList<Signature> signatureList = new ArrayList<>(); |
| int pos = readCertsListXml(parser, readSignatures, signatureList, count, false, builder); |
| Signature[] signatures = signatureList.toArray(new Signature[signatureList.size()]); |
| builder.setSignatures(signatures); |
| if (pos < count) { |
| PackageManagerService.reportSettingsProblem(Log.WARN, |
| "Error in package manager settings: <sigs> count does not match number of " |
| + " <cert> entries" + parser.getPositionDescription()); |
| } |
| |
| try { |
| mSigningDetails = builder.build(); |
| } catch (CertificateException e) { |
| PackageManagerService.reportSettingsProblem(Log.WARN, |
| "Error in package manager settings: <sigs> " |
| + "unable to convert certificate(s) to public key(s)."); |
| mSigningDetails = PackageParser.SigningDetails.UNKNOWN; |
| } |
| } |
| |
| private int readCertsListXml(XmlPullParser parser, ArrayList<Signature> readSignatures, |
| ArrayList<Signature> signatures, int count, boolean isPastSigs, |
| PackageParser.SigningDetails.Builder builder) |
| throws IOException, XmlPullParserException { |
| int pos = 0; |
| |
| int outerDepth = parser.getDepth(); |
| int type; |
| while ((type=parser.next()) != XmlPullParser.END_DOCUMENT |
| && (type != XmlPullParser.END_TAG |
| || parser.getDepth() > outerDepth)) { |
| if (type == XmlPullParser.END_TAG |
| || type == XmlPullParser.TEXT) { |
| continue; |
| } |
| |
| String tagName = parser.getName(); |
| if (tagName.equals("cert")) { |
| if (pos < count) { |
| String index = parser.getAttributeValue(null, "index"); |
| if (index != null) { |
| boolean signatureParsed = false; |
| try { |
| int idx = Integer.parseInt(index); |
| String key = parser.getAttributeValue(null, "key"); |
| if (key == null) { |
| if (idx >= 0 && idx < readSignatures.size()) { |
| Signature sig = readSignatures.get(idx); |
| if (sig != null) { |
| signatures.add(sig); |
| signatureParsed = true; |
| } else { |
| PackageManagerService.reportSettingsProblem(Log.WARN, |
| "Error in package manager settings: <cert> " |
| + "index " + index + " is not defined at " |
| + parser.getPositionDescription()); |
| } |
| } else { |
| PackageManagerService.reportSettingsProblem(Log.WARN, |
| "Error in package manager settings: <cert> " |
| + "index " + index + " is out of bounds at " |
| + parser.getPositionDescription()); |
| } |
| } else { |
| // Create the signature first to prevent adding null entries to the |
| // output List if the key value is invalid. |
| Signature sig = new Signature(key); |
| while (readSignatures.size() < idx) { |
| readSignatures.add(null); |
| } |
| readSignatures.add(sig); |
| signatures.add(sig); |
| signatureParsed = true; |
| } |
| } catch (NumberFormatException e) { |
| PackageManagerService.reportSettingsProblem(Log.WARN, |
| "Error in package manager settings: <cert> " |
| + "index " + index + " is not a number at " |
| + parser.getPositionDescription()); |
| } catch (IllegalArgumentException e) { |
| PackageManagerService.reportSettingsProblem(Log.WARN, |
| "Error in package manager settings: <cert> " |
| + "index " + index + " has an invalid signature at " |
| + parser.getPositionDescription() + ": " |
| + e.getMessage()); |
| } |
| |
| if (isPastSigs) { |
| String flagsStr = parser.getAttributeValue(null, "flags"); |
| if (flagsStr != null) { |
| try { |
| int flagsValue = Integer.parseInt(flagsStr); |
| // only modify the flags if the signature of the previous signer |
| // was successfully parsed above |
| if (signatureParsed) { |
| signatures.get(signatures.size() - 1).setFlags(flagsValue); |
| } else { |
| PackageManagerService.reportSettingsProblem(Log.WARN, |
| "Error in package manager settings: signature not " |
| + "available at index " |
| + pos + " to set flags at " |
| + parser.getPositionDescription()); |
| } |
| } catch (NumberFormatException e) { |
| PackageManagerService.reportSettingsProblem(Log.WARN, |
| "Error in package manager settings: <cert> " |
| + "flags " + flagsStr + " is not a number at " |
| + parser.getPositionDescription()); |
| } |
| } else { |
| PackageManagerService.reportSettingsProblem(Log.WARN, |
| "Error in package manager settings: <cert> has no" |
| + " flags at " + parser.getPositionDescription()); |
| } |
| } |
| } else { |
| PackageManagerService.reportSettingsProblem(Log.WARN, |
| "Error in package manager settings: <cert> has" |
| + " no index at " + parser.getPositionDescription()); |
| } |
| } else { |
| PackageManagerService.reportSettingsProblem(Log.WARN, |
| "Error in package manager settings: too " |
| + "many <cert> tags, expected " + count |
| + " at " + parser.getPositionDescription()); |
| } |
| pos++; |
| XmlUtils.skipCurrentTag(parser); |
| } else if (tagName.equals("pastSigs")) { |
| if (!isPastSigs) { |
| // we haven't encountered pastSigs yet, go ahead |
| String countStr = parser.getAttributeValue(null, "count"); |
| if (countStr == null) { |
| PackageManagerService.reportSettingsProblem(Log.WARN, |
| "Error in package manager settings: <pastSigs> has" |
| + " no count at " + parser.getPositionDescription()); |
| XmlUtils.skipCurrentTag(parser); |
| continue; |
| } |
| try { |
| final int pastSigsCount = Integer.parseInt(countStr); |
| ArrayList<Signature> pastSignatureList = new ArrayList<>(); |
| int pastSigsPos = readCertsListXml(parser, readSignatures, |
| pastSignatureList, |
| pastSigsCount, true, builder); |
| Signature[] pastSignatures = pastSignatureList.toArray( |
| new Signature[pastSignatureList.size()]); |
| builder = builder.setPastSigningCertificates(pastSignatures); |
| |
| if (pastSigsPos < pastSigsCount) { |
| PackageManagerService.reportSettingsProblem(Log.WARN, |
| "Error in package manager settings: <pastSigs> count does not " |
| + "match number of <cert> entries " |
| + parser.getPositionDescription()); |
| } |
| } catch (NumberFormatException e) { |
| PackageManagerService.reportSettingsProblem(Log.WARN, |
| "Error in package manager settings: <pastSigs> " |
| + "count " + countStr + " is not a number at " |
| + parser.getPositionDescription()); |
| } |
| } else { |
| PackageManagerService.reportSettingsProblem(Log.WARN, |
| "<pastSigs> encountered multiple times under the same <sigs> at " |
| + parser.getPositionDescription()); |
| XmlUtils.skipCurrentTag(parser); |
| } |
| } else { |
| PackageManagerService.reportSettingsProblem(Log.WARN, |
| "Unknown element under <sigs>: " |
| + parser.getName()); |
| XmlUtils.skipCurrentTag(parser); |
| } |
| } |
| return pos; |
| } |
| |
| @Override |
| public String toString() { |
| StringBuffer buf = new StringBuffer(128); |
| buf.append("PackageSignatures{"); |
| buf.append(Integer.toHexString(System.identityHashCode(this))); |
| buf.append(" version:"); |
| buf.append(mSigningDetails.signatureSchemeVersion); |
| buf.append(", signatures:["); |
| if (mSigningDetails.signatures != null) { |
| for (int i = 0; i < mSigningDetails.signatures.length; i++) { |
| if (i > 0) buf.append(", "); |
| buf.append(Integer.toHexString( |
| mSigningDetails.signatures[i].hashCode())); |
| } |
| } |
| buf.append("]"); |
| buf.append(", past signatures:["); |
| if (mSigningDetails.pastSigningCertificates != null) { |
| for (int i = 0; i < mSigningDetails.pastSigningCertificates.length; i++) { |
| if (i > 0) buf.append(", "); |
| buf.append(Integer.toHexString( |
| mSigningDetails.pastSigningCertificates[i].hashCode())); |
| buf.append(" flags: "); |
| buf.append( |
| Integer.toHexString(mSigningDetails.pastSigningCertificates[i].getFlags())); |
| } |
| } |
| buf.append("]}"); |
| return buf.toString(); |
| } |
| } |