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