• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 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 
17 package android.content.pm;
18 
19 import android.annotation.UnsupportedAppUsage;
20 import android.os.Parcel;
21 import android.os.Parcelable;
22 
23 import com.android.internal.util.ArrayUtils;
24 
25 import java.io.ByteArrayInputStream;
26 import java.io.InputStream;
27 import java.lang.ref.SoftReference;
28 import java.security.PublicKey;
29 import java.security.cert.Certificate;
30 import java.security.cert.CertificateEncodingException;
31 import java.security.cert.CertificateException;
32 import java.security.cert.CertificateFactory;
33 import java.security.cert.X509Certificate;
34 import java.util.Arrays;
35 
36 /**
37  * Opaque, immutable representation of a signing certificate associated with an
38  * application package.
39  * <p>
40  * This class name is slightly misleading, since it's not actually a signature.
41  */
42 public class Signature implements Parcelable {
43     private final byte[] mSignature;
44     private int mHashCode;
45     private boolean mHaveHashCode;
46     private SoftReference<String> mStringRef;
47     private Certificate[] mCertificateChain;
48     /**
49      * APK Signature Scheme v3 includes support for adding a proof-of-rotation record that
50      * contains two pieces of information:
51      *   1) the past signing certificates
52      *   2) the flags that APK wants to assign to each of the past signing certificates.
53      *
54      * These flags represent the second piece of information and are viewed as capabilities.
55      * They are an APK's way of telling the platform: "this is how I want to trust my old certs,
56      * please enforce that." This is useful for situation where this app itself is using its
57      * signing certificate as an authorization mechanism, like whether or not to allow another
58      * app to have its SIGNATURE permission.  An app could specify whether to allow other apps
59      * signed by its old cert 'X' to still get a signature permission it defines, for example.
60      */
61     private int mFlags;
62 
63     /**
64      * Create Signature from an existing raw byte array.
65      */
Signature(byte[] signature)66     public Signature(byte[] signature) {
67         mSignature = signature.clone();
68         mCertificateChain = null;
69     }
70 
71     /**
72      * Create signature from a certificate chain. Used for backward
73      * compatibility.
74      *
75      * @throws CertificateEncodingException
76      * @hide
77      */
Signature(Certificate[] certificateChain)78     public Signature(Certificate[] certificateChain) throws CertificateEncodingException {
79         mSignature = certificateChain[0].getEncoded();
80         if (certificateChain.length > 1) {
81             mCertificateChain = Arrays.copyOfRange(certificateChain, 1, certificateChain.length);
82         }
83     }
84 
parseHexDigit(int nibble)85     private static final int parseHexDigit(int nibble) {
86         if ('0' <= nibble && nibble <= '9') {
87             return nibble - '0';
88         } else if ('a' <= nibble && nibble <= 'f') {
89             return nibble - 'a' + 10;
90         } else if ('A' <= nibble && nibble <= 'F') {
91             return nibble - 'A' + 10;
92         } else {
93             throw new IllegalArgumentException("Invalid character " + nibble + " in hex string");
94         }
95     }
96 
97     /**
98      * Create Signature from a text representation previously returned by
99      * {@link #toChars} or {@link #toCharsString()}. Signatures are expected to
100      * be a hex-encoded ASCII string.
101      *
102      * @param text hex-encoded string representing the signature
103      * @throws IllegalArgumentException when signature is odd-length
104      */
Signature(String text)105     public Signature(String text) {
106         final byte[] input = text.getBytes();
107         final int N = input.length;
108 
109         if (N % 2 != 0) {
110             throw new IllegalArgumentException("text size " + N + " is not even");
111         }
112 
113         final byte[] sig = new byte[N / 2];
114         int sigIndex = 0;
115 
116         for (int i = 0; i < N;) {
117             final int hi = parseHexDigit(input[i++]);
118             final int lo = parseHexDigit(input[i++]);
119             sig[sigIndex++] = (byte) ((hi << 4) | lo);
120         }
121 
122         mSignature = sig;
123     }
124 
125     /**
126      * Sets the flags representing the capabilities of the past signing certificate.
127      * @hide
128      */
setFlags(int flags)129     public void setFlags(int flags) {
130         this.mFlags = flags;
131     }
132 
133     /**
134      * Returns the flags representing the capabilities of the past signing certificate.
135      * @hide
136      */
getFlags()137     public int getFlags() {
138         return mFlags;
139     }
140 
141     /**
142      * Encode the Signature as ASCII text.
143      */
toChars()144     public char[] toChars() {
145         return toChars(null, null);
146     }
147 
148     /**
149      * Encode the Signature as ASCII text in to an existing array.
150      *
151      * @param existingArray Existing char array or null.
152      * @param outLen Output parameter for the number of characters written in
153      * to the array.
154      * @return Returns either <var>existingArray</var> if it was large enough
155      * to hold the ASCII representation, or a newly created char[] array if
156      * needed.
157      */
toChars(char[] existingArray, int[] outLen)158     public char[] toChars(char[] existingArray, int[] outLen) {
159         byte[] sig = mSignature;
160         final int N = sig.length;
161         final int N2 = N*2;
162         char[] text = existingArray == null || N2 > existingArray.length
163                 ? new char[N2] : existingArray;
164         for (int j=0; j<N; j++) {
165             byte v = sig[j];
166             int d = (v>>4)&0xf;
167             text[j*2] = (char)(d >= 10 ? ('a' + d - 10) : ('0' + d));
168             d = v&0xf;
169             text[j*2+1] = (char)(d >= 10 ? ('a' + d - 10) : ('0' + d));
170         }
171         if (outLen != null) outLen[0] = N;
172         return text;
173     }
174 
175     /**
176      * Return the result of {@link #toChars()} as a String.
177      */
toCharsString()178     public String toCharsString() {
179         String str = mStringRef == null ? null : mStringRef.get();
180         if (str != null) {
181             return str;
182         }
183         str = new String(toChars());
184         mStringRef = new SoftReference<String>(str);
185         return str;
186     }
187 
188     /**
189      * @return the contents of this signature as a byte array.
190      */
toByteArray()191     public byte[] toByteArray() {
192         byte[] bytes = new byte[mSignature.length];
193         System.arraycopy(mSignature, 0, bytes, 0, mSignature.length);
194         return bytes;
195     }
196 
197     /**
198      * Returns the public key for this signature.
199      *
200      * @throws CertificateException when Signature isn't a valid X.509
201      *             certificate; shouldn't happen.
202      * @hide
203      */
204     @UnsupportedAppUsage
getPublicKey()205     public PublicKey getPublicKey() throws CertificateException {
206         final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
207         final ByteArrayInputStream bais = new ByteArrayInputStream(mSignature);
208         final Certificate cert = certFactory.generateCertificate(bais);
209         return cert.getPublicKey();
210     }
211 
212     /**
213      * Used for compatibility code that needs to check the certificate chain
214      * during upgrades.
215      *
216      * @throws CertificateEncodingException
217      * @hide
218      */
getChainSignatures()219     public Signature[] getChainSignatures() throws CertificateEncodingException {
220         if (mCertificateChain == null) {
221             return new Signature[] { this };
222         }
223 
224         Signature[] chain = new Signature[1 + mCertificateChain.length];
225         chain[0] = this;
226 
227         int i = 1;
228         for (Certificate c : mCertificateChain) {
229             chain[i++] = new Signature(c.getEncoded());
230         }
231 
232         return chain;
233     }
234 
235     @Override
equals(Object obj)236     public boolean equals(Object obj) {
237         try {
238             if (obj != null) {
239                 Signature other = (Signature)obj;
240                 return this == other || Arrays.equals(mSignature, other.mSignature);
241             }
242         } catch (ClassCastException e) {
243         }
244         return false;
245     }
246 
247     @Override
hashCode()248     public int hashCode() {
249         if (mHaveHashCode) {
250             return mHashCode;
251         }
252         mHashCode = Arrays.hashCode(mSignature);
253         mHaveHashCode = true;
254         return mHashCode;
255     }
256 
describeContents()257     public int describeContents() {
258         return 0;
259     }
260 
writeToParcel(Parcel dest, int parcelableFlags)261     public void writeToParcel(Parcel dest, int parcelableFlags) {
262         dest.writeByteArray(mSignature);
263     }
264 
265     public static final @android.annotation.NonNull Parcelable.Creator<Signature> CREATOR
266             = new Parcelable.Creator<Signature>() {
267         public Signature createFromParcel(Parcel source) {
268             return new Signature(source);
269         }
270 
271         public Signature[] newArray(int size) {
272             return new Signature[size];
273         }
274     };
275 
Signature(Parcel source)276     private Signature(Parcel source) {
277         mSignature = source.createByteArray();
278     }
279 
280     /**
281      * Test if given {@link Signature} sets are exactly equal.
282      *
283      * @hide
284      */
areExactMatch(Signature[] a, Signature[] b)285     public static boolean areExactMatch(Signature[] a, Signature[] b) {
286         return (a.length == b.length) && ArrayUtils.containsAll(a, b)
287                 && ArrayUtils.containsAll(b, a);
288     }
289 
290     /**
291      * Test if given {@link Signature} sets are effectively equal. In rare
292      * cases, certificates can have slightly malformed encoding which causes
293      * exact-byte checks to fail.
294      * <p>
295      * To identify effective equality, we bounce the certificates through an
296      * decode/encode pass before doing the exact-byte check. To reduce attack
297      * surface area, we only allow a byte size delta of a few bytes.
298      *
299      * @throws CertificateException if the before/after length differs
300      *             substantially, usually a signal of something fishy going on.
301      * @hide
302      */
areEffectiveMatch(Signature[] a, Signature[] b)303     public static boolean areEffectiveMatch(Signature[] a, Signature[] b)
304             throws CertificateException {
305         final CertificateFactory cf = CertificateFactory.getInstance("X.509");
306 
307         final Signature[] aPrime = new Signature[a.length];
308         for (int i = 0; i < a.length; i++) {
309             aPrime[i] = bounce(cf, a[i]);
310         }
311         final Signature[] bPrime = new Signature[b.length];
312         for (int i = 0; i < b.length; i++) {
313             bPrime[i] = bounce(cf, b[i]);
314         }
315 
316         return areExactMatch(aPrime, bPrime);
317     }
318 
319     /**
320      * Test if given {@link Signature} objects are effectively equal. In rare
321      * cases, certificates can have slightly malformed encoding which causes
322      * exact-byte checks to fail.
323      * <p>
324      * To identify effective equality, we bounce the certificates through an
325      * decode/encode pass before doing the exact-byte check. To reduce attack
326      * surface area, we only allow a byte size delta of a few bytes.
327      *
328      * @throws CertificateException if the before/after length differs
329      *             substantially, usually a signal of something fishy going on.
330      * @hide
331      */
areEffectiveMatch(Signature a, Signature b)332     public static boolean areEffectiveMatch(Signature a, Signature b)
333             throws CertificateException {
334         final CertificateFactory cf = CertificateFactory.getInstance("X.509");
335 
336         final Signature aPrime = bounce(cf, a);
337         final Signature bPrime = bounce(cf, b);
338 
339         return aPrime.equals(bPrime);
340     }
341 
342     /**
343      * Bounce the given {@link Signature} through a decode/encode cycle.
344      *
345      * @throws CertificateException if the before/after length differs
346      *             substantially, usually a signal of something fishy going on.
347      * @hide
348      */
bounce(CertificateFactory cf, Signature s)349     public static Signature bounce(CertificateFactory cf, Signature s) throws CertificateException {
350         final InputStream is = new ByteArrayInputStream(s.mSignature);
351         final X509Certificate cert = (X509Certificate) cf.generateCertificate(is);
352         final Signature sPrime = new Signature(cert.getEncoded());
353 
354         if (Math.abs(sPrime.mSignature.length - s.mSignature.length) > 2) {
355             throw new CertificateException("Bounced cert length looks fishy; before "
356                     + s.mSignature.length + ", after " + sPrime.mSignature.length);
357         }
358 
359         return sPrime;
360     }
361 }