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.os.Parcel; 20 import android.os.Parcelable; 21 22 import com.android.internal.util.ArrayUtils; 23 24 import java.io.ByteArrayInputStream; 25 import java.io.InputStream; 26 import java.lang.ref.SoftReference; 27 import java.security.PublicKey; 28 import java.security.cert.Certificate; 29 import java.security.cert.CertificateEncodingException; 30 import java.security.cert.CertificateException; 31 import java.security.cert.CertificateFactory; 32 import java.security.cert.X509Certificate; 33 import java.util.Arrays; 34 35 /** 36 * Opaque, immutable representation of a signing certificate associated with an 37 * application package. 38 * <p> 39 * This class name is slightly misleading, since it's not actually a signature. 40 */ 41 public class Signature implements Parcelable { 42 private final byte[] mSignature; 43 private int mHashCode; 44 private boolean mHaveHashCode; 45 private SoftReference<String> mStringRef; 46 private Certificate[] mCertificateChain; 47 48 /** 49 * Create Signature from an existing raw byte array. 50 */ Signature(byte[] signature)51 public Signature(byte[] signature) { 52 mSignature = signature.clone(); 53 mCertificateChain = null; 54 } 55 56 /** 57 * Create signature from a certificate chain. Used for backward 58 * compatibility. 59 * 60 * @throws CertificateEncodingException 61 * @hide 62 */ Signature(Certificate[] certificateChain)63 public Signature(Certificate[] certificateChain) throws CertificateEncodingException { 64 mSignature = certificateChain[0].getEncoded(); 65 if (certificateChain.length > 1) { 66 mCertificateChain = Arrays.copyOfRange(certificateChain, 1, certificateChain.length); 67 } 68 } 69 parseHexDigit(int nibble)70 private static final int parseHexDigit(int nibble) { 71 if ('0' <= nibble && nibble <= '9') { 72 return nibble - '0'; 73 } else if ('a' <= nibble && nibble <= 'f') { 74 return nibble - 'a' + 10; 75 } else if ('A' <= nibble && nibble <= 'F') { 76 return nibble - 'A' + 10; 77 } else { 78 throw new IllegalArgumentException("Invalid character " + nibble + " in hex string"); 79 } 80 } 81 82 /** 83 * Create Signature from a text representation previously returned by 84 * {@link #toChars} or {@link #toCharsString()}. Signatures are expected to 85 * be a hex-encoded ASCII string. 86 * 87 * @param text hex-encoded string representing the signature 88 * @throws IllegalArgumentException when signature is odd-length 89 */ Signature(String text)90 public Signature(String text) { 91 final byte[] input = text.getBytes(); 92 final int N = input.length; 93 94 if (N % 2 != 0) { 95 throw new IllegalArgumentException("text size " + N + " is not even"); 96 } 97 98 final byte[] sig = new byte[N / 2]; 99 int sigIndex = 0; 100 101 for (int i = 0; i < N;) { 102 final int hi = parseHexDigit(input[i++]); 103 final int lo = parseHexDigit(input[i++]); 104 sig[sigIndex++] = (byte) ((hi << 4) | lo); 105 } 106 107 mSignature = sig; 108 } 109 110 /** 111 * Encode the Signature as ASCII text. 112 */ toChars()113 public char[] toChars() { 114 return toChars(null, null); 115 } 116 117 /** 118 * Encode the Signature as ASCII text in to an existing array. 119 * 120 * @param existingArray Existing char array or null. 121 * @param outLen Output parameter for the number of characters written in 122 * to the array. 123 * @return Returns either <var>existingArray</var> if it was large enough 124 * to hold the ASCII representation, or a newly created char[] array if 125 * needed. 126 */ toChars(char[] existingArray, int[] outLen)127 public char[] toChars(char[] existingArray, int[] outLen) { 128 byte[] sig = mSignature; 129 final int N = sig.length; 130 final int N2 = N*2; 131 char[] text = existingArray == null || N2 > existingArray.length 132 ? new char[N2] : existingArray; 133 for (int j=0; j<N; j++) { 134 byte v = sig[j]; 135 int d = (v>>4)&0xf; 136 text[j*2] = (char)(d >= 10 ? ('a' + d - 10) : ('0' + d)); 137 d = v&0xf; 138 text[j*2+1] = (char)(d >= 10 ? ('a' + d - 10) : ('0' + d)); 139 } 140 if (outLen != null) outLen[0] = N; 141 return text; 142 } 143 144 /** 145 * Return the result of {@link #toChars()} as a String. 146 */ toCharsString()147 public String toCharsString() { 148 String str = mStringRef == null ? null : mStringRef.get(); 149 if (str != null) { 150 return str; 151 } 152 str = new String(toChars()); 153 mStringRef = new SoftReference<String>(str); 154 return str; 155 } 156 157 /** 158 * @return the contents of this signature as a byte array. 159 */ toByteArray()160 public byte[] toByteArray() { 161 byte[] bytes = new byte[mSignature.length]; 162 System.arraycopy(mSignature, 0, bytes, 0, mSignature.length); 163 return bytes; 164 } 165 166 /** 167 * Returns the public key for this signature. 168 * 169 * @throws CertificateException when Signature isn't a valid X.509 170 * certificate; shouldn't happen. 171 * @hide 172 */ getPublicKey()173 public PublicKey getPublicKey() throws CertificateException { 174 final CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); 175 final ByteArrayInputStream bais = new ByteArrayInputStream(mSignature); 176 final Certificate cert = certFactory.generateCertificate(bais); 177 return cert.getPublicKey(); 178 } 179 180 /** 181 * Used for compatibility code that needs to check the certificate chain 182 * during upgrades. 183 * 184 * @throws CertificateEncodingException 185 * @hide 186 */ getChainSignatures()187 public Signature[] getChainSignatures() throws CertificateEncodingException { 188 if (mCertificateChain == null) { 189 return new Signature[] { this }; 190 } 191 192 Signature[] chain = new Signature[1 + mCertificateChain.length]; 193 chain[0] = this; 194 195 int i = 1; 196 for (Certificate c : mCertificateChain) { 197 chain[i++] = new Signature(c.getEncoded()); 198 } 199 200 return chain; 201 } 202 203 @Override equals(Object obj)204 public boolean equals(Object obj) { 205 try { 206 if (obj != null) { 207 Signature other = (Signature)obj; 208 return this == other || Arrays.equals(mSignature, other.mSignature); 209 } 210 } catch (ClassCastException e) { 211 } 212 return false; 213 } 214 215 @Override hashCode()216 public int hashCode() { 217 if (mHaveHashCode) { 218 return mHashCode; 219 } 220 mHashCode = Arrays.hashCode(mSignature); 221 mHaveHashCode = true; 222 return mHashCode; 223 } 224 describeContents()225 public int describeContents() { 226 return 0; 227 } 228 writeToParcel(Parcel dest, int parcelableFlags)229 public void writeToParcel(Parcel dest, int parcelableFlags) { 230 dest.writeByteArray(mSignature); 231 } 232 233 public static final Parcelable.Creator<Signature> CREATOR 234 = new Parcelable.Creator<Signature>() { 235 public Signature createFromParcel(Parcel source) { 236 return new Signature(source); 237 } 238 239 public Signature[] newArray(int size) { 240 return new Signature[size]; 241 } 242 }; 243 Signature(Parcel source)244 private Signature(Parcel source) { 245 mSignature = source.createByteArray(); 246 } 247 248 /** 249 * Test if given {@link Signature} sets are exactly equal. 250 * 251 * @hide 252 */ areExactMatch(Signature[] a, Signature[] b)253 public static boolean areExactMatch(Signature[] a, Signature[] b) { 254 return (a.length == b.length) && ArrayUtils.containsAll(a, b) 255 && ArrayUtils.containsAll(b, a); 256 } 257 258 /** 259 * Test if given {@link Signature} sets are effectively equal. In rare 260 * cases, certificates can have slightly malformed encoding which causes 261 * exact-byte checks to fail. 262 * <p> 263 * To identify effective equality, we bounce the certificates through an 264 * decode/encode pass before doing the exact-byte check. To reduce attack 265 * surface area, we only allow a byte size delta of a few bytes. 266 * 267 * @throws CertificateException if the before/after length differs 268 * substantially, usually a signal of something fishy going on. 269 * @hide 270 */ areEffectiveMatch(Signature[] a, Signature[] b)271 public static boolean areEffectiveMatch(Signature[] a, Signature[] b) 272 throws CertificateException { 273 final CertificateFactory cf = CertificateFactory.getInstance("X.509"); 274 275 final Signature[] aPrime = new Signature[a.length]; 276 for (int i = 0; i < a.length; i++) { 277 aPrime[i] = bounce(cf, a[i]); 278 } 279 final Signature[] bPrime = new Signature[b.length]; 280 for (int i = 0; i < b.length; i++) { 281 bPrime[i] = bounce(cf, b[i]); 282 } 283 284 return areExactMatch(aPrime, bPrime); 285 } 286 287 /** 288 * Test if given {@link Signature} objects are effectively equal. In rare 289 * cases, certificates can have slightly malformed encoding which causes 290 * exact-byte checks to fail. 291 * <p> 292 * To identify effective equality, we bounce the certificates through an 293 * decode/encode pass before doing the exact-byte check. To reduce attack 294 * surface area, we only allow a byte size delta of a few bytes. 295 * 296 * @throws CertificateException if the before/after length differs 297 * substantially, usually a signal of something fishy going on. 298 * @hide 299 */ areEffectiveMatch(Signature a, Signature b)300 public static boolean areEffectiveMatch(Signature a, Signature b) 301 throws CertificateException { 302 final CertificateFactory cf = CertificateFactory.getInstance("X.509"); 303 304 final Signature aPrime = bounce(cf, a); 305 final Signature bPrime = bounce(cf, b); 306 307 return aPrime.equals(bPrime); 308 } 309 310 /** 311 * Bounce the given {@link Signature} through a decode/encode cycle. 312 * 313 * @throws CertificateException if the before/after length differs 314 * substantially, usually a signal of something fishy going on. 315 * @hide 316 */ bounce(CertificateFactory cf, Signature s)317 public static Signature bounce(CertificateFactory cf, Signature s) throws CertificateException { 318 final InputStream is = new ByteArrayInputStream(s.mSignature); 319 final X509Certificate cert = (X509Certificate) cf.generateCertificate(is); 320 final Signature sPrime = new Signature(cert.getEncoded()); 321 322 if (Math.abs(sPrime.mSignature.length - s.mSignature.length) > 2) { 323 throw new CertificateException("Bounced cert length looks fishy; before " 324 + s.mSignature.length + ", after " + sPrime.mSignature.length); 325 } 326 327 return sPrime; 328 } 329 } 330