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.compat.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      * Copy constructor that creates a new instance from the provided {@code other} Signature.
127      *
128      * @hide
129      */
Signature(Signature other)130     public Signature(Signature other) {
131         mSignature = other.mSignature.clone();
132         Certificate[] otherCertificateChain = other.mCertificateChain;
133         if (otherCertificateChain != null && otherCertificateChain.length > 1) {
134             mCertificateChain = Arrays.copyOfRange(otherCertificateChain, 1,
135                     otherCertificateChain.length);
136         }
137         mFlags = other.mFlags;
138     }
139 
140     /**
141      * Sets the flags representing the capabilities of the past signing certificate.
142      * @hide
143      */
setFlags(int flags)144     public void setFlags(int flags) {
145         this.mFlags = flags;
146     }
147 
148     /**
149      * Returns the flags representing the capabilities of the past signing certificate.
150      * @hide
151      */
getFlags()152     public int getFlags() {
153         return mFlags;
154     }
155 
156     /**
157      * Encode the Signature as ASCII text.
158      */
toChars()159     public char[] toChars() {
160         return toChars(null, null);
161     }
162 
163     /**
164      * Encode the Signature as ASCII text in to an existing array.
165      *
166      * @param existingArray Existing char array or null.
167      * @param outLen Output parameter for the number of characters written in
168      * to the array.
169      * @return Returns either <var>existingArray</var> if it was large enough
170      * to hold the ASCII representation, or a newly created char[] array if
171      * needed.
172      */
toChars(char[] existingArray, int[] outLen)173     public char[] toChars(char[] existingArray, int[] outLen) {
174         byte[] sig = mSignature;
175         final int N = sig.length;
176         final int N2 = N*2;
177         char[] text = existingArray == null || N2 > existingArray.length
178                 ? new char[N2] : existingArray;
179         for (int j=0; j<N; j++) {
180             byte v = sig[j];
181             int d = (v>>4)&0xf;
182             text[j*2] = (char)(d >= 10 ? ('a' + d - 10) : ('0' + d));
183             d = v&0xf;
184             text[j*2+1] = (char)(d >= 10 ? ('a' + d - 10) : ('0' + d));
185         }
186         if (outLen != null) outLen[0] = N;
187         return text;
188     }
189 
190     /**
191      * Return the result of {@link #toChars()} as a String.
192      */
toCharsString()193     public String toCharsString() {
194         String str = mStringRef == null ? null : mStringRef.get();
195         if (str != null) {
196             return str;
197         }
198         str = new String(toChars());
199         mStringRef = new SoftReference<String>(str);
200         return str;
201     }
202 
203     /**
204      * @return the contents of this signature as a byte array.
205      */
toByteArray()206     public byte[] toByteArray() {
207         byte[] bytes = new byte[mSignature.length];
208         System.arraycopy(mSignature, 0, bytes, 0, mSignature.length);
209         return bytes;
210     }
211 
212     /**
213      * Returns the public key for this signature.
214      *
215      * @throws CertificateException when Signature isn't a valid X.509
216      *             certificate; shouldn't happen.
217      * @hide
218      */
219     @UnsupportedAppUsage
getPublicKey()220     public PublicKey getPublicKey() throws CertificateException {
221         final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
222         final ByteArrayInputStream bais = new ByteArrayInputStream(mSignature);
223         final Certificate cert = certFactory.generateCertificate(bais);
224         return cert.getPublicKey();
225     }
226 
227     /**
228      * Used for compatibility code that needs to check the certificate chain
229      * during upgrades.
230      *
231      * @throws CertificateEncodingException
232      * @hide
233      */
getChainSignatures()234     public Signature[] getChainSignatures() throws CertificateEncodingException {
235         if (mCertificateChain == null) {
236             return new Signature[] { this };
237         }
238 
239         Signature[] chain = new Signature[1 + mCertificateChain.length];
240         chain[0] = this;
241 
242         int i = 1;
243         for (Certificate c : mCertificateChain) {
244             chain[i++] = new Signature(c.getEncoded());
245         }
246 
247         return chain;
248     }
249 
250     @Override
equals(Object obj)251     public boolean equals(Object obj) {
252         try {
253             if (obj != null) {
254                 Signature other = (Signature)obj;
255                 return this == other || Arrays.equals(mSignature, other.mSignature);
256             }
257         } catch (ClassCastException e) {
258         }
259         return false;
260     }
261 
262     @Override
hashCode()263     public int hashCode() {
264         if (mHaveHashCode) {
265             return mHashCode;
266         }
267         mHashCode = Arrays.hashCode(mSignature);
268         mHaveHashCode = true;
269         return mHashCode;
270     }
271 
describeContents()272     public int describeContents() {
273         return 0;
274     }
275 
writeToParcel(Parcel dest, int parcelableFlags)276     public void writeToParcel(Parcel dest, int parcelableFlags) {
277         dest.writeByteArray(mSignature);
278     }
279 
280     public static final @android.annotation.NonNull Parcelable.Creator<Signature> CREATOR
281             = new Parcelable.Creator<Signature>() {
282         public Signature createFromParcel(Parcel source) {
283             return new Signature(source);
284         }
285 
286         public Signature[] newArray(int size) {
287             return new Signature[size];
288         }
289     };
290 
Signature(Parcel source)291     private Signature(Parcel source) {
292         mSignature = source.createByteArray();
293     }
294 
295     /**
296      * Test if given {@link Signature} sets are exactly equal.
297      *
298      * @hide
299      */
areExactMatch(Signature[] a, Signature[] b)300     public static boolean areExactMatch(Signature[] a, Signature[] b) {
301         return (a.length == b.length) && ArrayUtils.containsAll(a, b)
302                 && ArrayUtils.containsAll(b, a);
303     }
304 
305     /**
306      * Test if given {@link Signature} sets are effectively equal. In rare
307      * cases, certificates can have slightly malformed encoding which causes
308      * exact-byte checks to fail.
309      * <p>
310      * To identify effective equality, we bounce the certificates through an
311      * decode/encode pass before doing the exact-byte check. To reduce attack
312      * surface area, we only allow a byte size delta of a few bytes.
313      *
314      * @throws CertificateException if the before/after length differs
315      *             substantially, usually a signal of something fishy going on.
316      * @hide
317      */
areEffectiveMatch(Signature[] a, Signature[] b)318     public static boolean areEffectiveMatch(Signature[] a, Signature[] b)
319             throws CertificateException {
320         final CertificateFactory cf = CertificateFactory.getInstance("X.509");
321 
322         final Signature[] aPrime = new Signature[a.length];
323         for (int i = 0; i < a.length; i++) {
324             aPrime[i] = bounce(cf, a[i]);
325         }
326         final Signature[] bPrime = new Signature[b.length];
327         for (int i = 0; i < b.length; i++) {
328             bPrime[i] = bounce(cf, b[i]);
329         }
330 
331         return areExactMatch(aPrime, bPrime);
332     }
333 
334     /**
335      * Test if given {@link Signature} objects are effectively equal. In rare
336      * cases, certificates can have slightly malformed encoding which causes
337      * exact-byte checks to fail.
338      * <p>
339      * To identify effective equality, we bounce the certificates through an
340      * decode/encode pass before doing the exact-byte check. To reduce attack
341      * surface area, we only allow a byte size delta of a few bytes.
342      *
343      * @throws CertificateException if the before/after length differs
344      *             substantially, usually a signal of something fishy going on.
345      * @hide
346      */
areEffectiveMatch(Signature a, Signature b)347     public static boolean areEffectiveMatch(Signature a, Signature b)
348             throws CertificateException {
349         final CertificateFactory cf = CertificateFactory.getInstance("X.509");
350 
351         final Signature aPrime = bounce(cf, a);
352         final Signature bPrime = bounce(cf, b);
353 
354         return aPrime.equals(bPrime);
355     }
356 
357     /**
358      * Bounce the given {@link Signature} through a decode/encode cycle.
359      *
360      * @throws CertificateException if the before/after length differs
361      *             substantially, usually a signal of something fishy going on.
362      * @hide
363      */
bounce(CertificateFactory cf, Signature s)364     public static Signature bounce(CertificateFactory cf, Signature s) throws CertificateException {
365         final InputStream is = new ByteArrayInputStream(s.mSignature);
366         final X509Certificate cert = (X509Certificate) cf.generateCertificate(is);
367         final Signature sPrime = new Signature(cert.getEncoded());
368 
369         if (Math.abs(sPrime.mSignature.length - s.mSignature.length) > 2) {
370             throw new CertificateException("Bounced cert length looks fishy; before "
371                     + s.mSignature.length + ", after " + sPrime.mSignature.length);
372         }
373 
374         return sPrime;
375     }
376 }