blob: 349bb693bd9c9054700986abc1e24ae8fba85086 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
17package android.content.pm;
18
Mathew Inwood5c0d3542018-08-14 13:54:31 +010019import android.annotation.UnsupportedAppUsage;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080020import android.os.Parcel;
21import android.os.Parcelable;
22
Jeff Sharkey94c91dc2013-03-06 16:25:50 -080023import com.android.internal.util.ArrayUtils;
24
Kenny Root05ca4c92011-09-15 10:36:25 -070025import java.io.ByteArrayInputStream;
Jeff Sharkeyd68b87c2014-11-12 12:18:11 -080026import java.io.InputStream;
Kenny Rootde0ff632010-09-03 16:02:27 -070027import java.lang.ref.SoftReference;
Kenny Root05ca4c92011-09-15 10:36:25 -070028import java.security.PublicKey;
29import java.security.cert.Certificate;
Kenny Roota8e65fd2014-04-24 11:44:47 -070030import java.security.cert.CertificateEncodingException;
Kenny Root05ca4c92011-09-15 10:36:25 -070031import java.security.cert.CertificateException;
32import java.security.cert.CertificateFactory;
Jeff Sharkeyd68b87c2014-11-12 12:18:11 -080033import java.security.cert.X509Certificate;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080034import java.util.Arrays;
35
36/**
Jeff Sharkey3a44f3f2014-04-28 17:36:31 -070037 * Opaque, immutable representation of a signing certificate associated with an
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080038 * application package.
Jeff Sharkey3a44f3f2014-04-28 17:36:31 -070039 * <p>
40 * This class name is slightly misleading, since it's not actually a signature.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080041 */
42public class Signature implements Parcelable {
43 private final byte[] mSignature;
44 private int mHashCode;
45 private boolean mHaveHashCode;
Kenny Rootde0ff632010-09-03 16:02:27 -070046 private SoftReference<String> mStringRef;
Kenny Roota8e65fd2014-04-24 11:44:47 -070047 private Certificate[] mCertificateChain;
Michael Groovera117b0d2018-07-23 12:55:54 -070048 /**
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;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080062
63 /**
64 * Create Signature from an existing raw byte array.
65 */
66 public Signature(byte[] signature) {
67 mSignature = signature.clone();
Kenny Roota8e65fd2014-04-24 11:44:47 -070068 mCertificateChain = null;
69 }
70
71 /**
72 * Create signature from a certificate chain. Used for backward
73 * compatibility.
74 *
75 * @throws CertificateEncodingException
76 * @hide
77 */
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 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080083 }
84
Kenny Root11373412011-07-28 15:13:33 -070085 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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080097 /**
98 * Create Signature from a text representation previously returned by
Kenny Root11373412011-07-28 15:13:33 -070099 * {@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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800104 */
105 public Signature(String text) {
Kenny Rootd21d4442011-07-22 15:32:30 -0700106 final byte[] input = text.getBytes();
107 final int N = input.length;
Kenny Root11373412011-07-28 15:13:33 -0700108
109 if (N % 2 != 0) {
110 throw new IllegalArgumentException("text size " + N + " is not even");
111 }
112
Kenny Rootd21d4442011-07-22 15:32:30 -0700113 final byte[] sig = new byte[N / 2];
114 int sigIndex = 0;
115
116 for (int i = 0; i < N;) {
Kenny Root11373412011-07-28 15:13:33 -0700117 final int hi = parseHexDigit(input[i++]);
118 final int lo = parseHexDigit(input[i++]);
119 sig[sigIndex++] = (byte) ((hi << 4) | lo);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800120 }
Kenny Rootd21d4442011-07-22 15:32:30 -0700121
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800122 mSignature = sig;
123 }
124
125 /**
Michael Groovera117b0d2018-07-23 12:55:54 -0700126 * Sets the flags representing the capabilities of the past signing certificate.
127 * @hide
128 */
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 */
137 public int getFlags() {
138 return mFlags;
139 }
140
141 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800142 * Encode the Signature as ASCII text.
143 */
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 */
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 /**
Kenny Root11373412011-07-28 15:13:33 -0700176 * Return the result of {@link #toChars()} as a String.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800177 */
178 public String toCharsString() {
Kenny Rootde0ff632010-09-03 16:02:27 -0700179 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;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800186 }
187
188 /**
189 * @return the contents of this signature as a byte array.
190 */
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
Kenny Root05ca4c92011-09-15 10:36:25 -0700197 /**
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 */
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100204 @UnsupportedAppUsage
Kenny Root05ca4c92011-09-15 10:36:25 -0700205 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
Kenny Roota8e65fd2014-04-24 11:44:47 -0700212 /**
213 * Used for compatibility code that needs to check the certificate chain
214 * during upgrades.
215 *
216 * @throws CertificateEncodingException
217 * @hide
218 */
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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800235 @Override
236 public boolean equals(Object obj) {
237 try {
238 if (obj != null) {
239 Signature other = (Signature)obj;
Kenny Root11373412011-07-28 15:13:33 -0700240 return this == other || Arrays.equals(mSignature, other.mSignature);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800241 }
242 } catch (ClassCastException e) {
243 }
244 return false;
245 }
246
247 @Override
248 public int hashCode() {
249 if (mHaveHashCode) {
250 return mHashCode;
251 }
252 mHashCode = Arrays.hashCode(mSignature);
253 mHaveHashCode = true;
254 return mHashCode;
255 }
256
257 public int describeContents() {
258 return 0;
259 }
260
261 public void writeToParcel(Parcel dest, int parcelableFlags) {
262 dest.writeByteArray(mSignature);
263 }
264
265 public static final 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
276 private Signature(Parcel source) {
277 mSignature = source.createByteArray();
278 }
Jeff Sharkey94c91dc2013-03-06 16:25:50 -0800279
280 /**
281 * Test if given {@link Signature} sets are exactly equal.
282 *
283 * @hide
284 */
285 public static boolean areExactMatch(Signature[] a, Signature[] b) {
Jeff Sharkeyec55ef02014-07-08 11:28:00 -0700286 return (a.length == b.length) && ArrayUtils.containsAll(a, b)
287 && ArrayUtils.containsAll(b, a);
Jeff Sharkey94c91dc2013-03-06 16:25:50 -0800288 }
Jeff Sharkeyd68b87c2014-11-12 12:18:11 -0800289
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 */
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 /**
Dan Cashman1dbe6d02018-01-23 11:18:28 -0800320 * 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 */
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 /**
Jeff Sharkeyd68b87c2014-11-12 12:18:11 -0800343 * 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 */
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 }
Michael Groovera117b0d2018-07-23 12:55:54 -0700361}