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