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