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 }