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.lang.ref.SoftReference; 26 import java.security.PublicKey; 27 import java.security.cert.Certificate; 28 import java.security.cert.CertificateEncodingException; 29 import java.security.cert.CertificateException; 30 import java.security.cert.CertificateFactory; 31 import java.util.Arrays; 32 33 /** 34 * Opaque, immutable representation of a signing certificate associated with an 35 * application package. 36 * <p> 37 * This class name is slightly misleading, since it's not actually a signature. 38 */ 39 public class Signature implements Parcelable { 40 private final byte[] mSignature; 41 private int mHashCode; 42 private boolean mHaveHashCode; 43 private SoftReference<String> mStringRef; 44 private Certificate[] mCertificateChain; 45 46 /** 47 * Create Signature from an existing raw byte array. 48 */ 49 public Signature(byte[] signature) { 50 mSignature = signature.clone(); 51 mCertificateChain = null; 52 } 53 54 /** 55 * Create signature from a certificate chain. Used for backward 56 * compatibility. 57 * 58 * @throws CertificateEncodingException 59 * @hide 60 */ 61 public Signature(Certificate[] certificateChain) throws CertificateEncodingException { 62 mSignature = certificateChain[0].getEncoded(); 63 if (certificateChain.length > 1) { 64 mCertificateChain = Arrays.copyOfRange(certificateChain, 1, certificateChain.length); 65 } 66 } 67 68 private static final int parseHexDigit(int nibble) { 69 if ('0' <= nibble && nibble <= '9') { 70 return nibble - '0'; 71 } else if ('a' <= nibble && nibble <= 'f') { 72 return nibble - 'a' + 10; 73 } else if ('A' <= nibble && nibble <= 'F') { 74 return nibble - 'A' + 10; 75 } else { 76 throw new IllegalArgumentException("Invalid character " + nibble + " in hex string"); 77 } 78 } 79 80 /** 81 * Create Signature from a text representation previously returned by 82 * {@link #toChars} or {@link #toCharsString()}. Signatures are expected to 83 * be a hex-encoded ASCII string. 84 * 85 * @param text hex-encoded string representing the signature 86 * @throws IllegalArgumentException when signature is odd-length 87 */ 88 public Signature(String text) { 89 final byte[] input = text.getBytes(); 90 final int N = input.length; 91 92 if (N % 2 != 0) { 93 throw new IllegalArgumentException("text size " + N + " is not even"); 94 } 95 96 final byte[] sig = new byte[N / 2]; 97 int sigIndex = 0; 98 99 for (int i = 0; i < N;) { 100 final int hi = parseHexDigit(input[i++]); 101 final int lo = parseHexDigit(input[i++]); 102 sig[sigIndex++] = (byte) ((hi << 4) | lo); 103 } 104 105 mSignature = sig; 106 } 107 108 /** 109 * Encode the Signature as ASCII text. 110 */ 111 public char[] toChars() { 112 return toChars(null, null); 113 } 114 115 /** 116 * Encode the Signature as ASCII text in to an existing array. 117 * 118 * @param existingArray Existing char array or null. 119 * @param outLen Output parameter for the number of characters written in 120 * to the array. 121 * @return Returns either <var>existingArray</var> if it was large enough 122 * to hold the ASCII representation, or a newly created char[] array if 123 * needed. 124 */ 125 public char[] toChars(char[] existingArray, int[] outLen) { 126 byte[] sig = mSignature; 127 final int N = sig.length; 128 final int N2 = N*2; 129 char[] text = existingArray == null || N2 > existingArray.length 130 ? new char[N2] : existingArray; 131 for (int j=0; j<N; j++) { 132 byte v = sig[j]; 133 int d = (v>>4)&0xf; 134 text[j*2] = (char)(d >= 10 ? ('a' + d - 10) : ('0' + d)); 135 d = v&0xf; 136 text[j*2+1] = (char)(d >= 10 ? ('a' + d - 10) : ('0' + d)); 137 } 138 if (outLen != null) outLen[0] = N; 139 return text; 140 } 141 142 /** 143 * Return the result of {@link #toChars()} as a String. 144 */ 145 public String toCharsString() { 146 String str = mStringRef == null ? null : mStringRef.get(); 147 if (str != null) { 148 return str; 149 } 150 str = new String(toChars()); 151 mStringRef = new SoftReference<String>(str); 152 return str; 153 } 154 155 /** 156 * @return the contents of this signature as a byte array. 157 */ 158 public byte[] toByteArray() { 159 byte[] bytes = new byte[mSignature.length]; 160 System.arraycopy(mSignature, 0, bytes, 0, mSignature.length); 161 return bytes; 162 } 163 164 /** 165 * Returns the public key for this signature. 166 * 167 * @throws CertificateException when Signature isn't a valid X.509 168 * certificate; shouldn't happen. 169 * @hide 170 */ 171 public PublicKey getPublicKey() throws CertificateException { 172 final CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); 173 final ByteArrayInputStream bais = new ByteArrayInputStream(mSignature); 174 final Certificate cert = certFactory.generateCertificate(bais); 175 return cert.getPublicKey(); 176 } 177 178 /** 179 * Used for compatibility code that needs to check the certificate chain 180 * during upgrades. 181 * 182 * @throws CertificateEncodingException 183 * @hide 184 */ 185 public Signature[] getChainSignatures() throws CertificateEncodingException { 186 if (mCertificateChain == null) { 187 return new Signature[] { this }; 188 } 189 190 Signature[] chain = new Signature[1 + mCertificateChain.length]; 191 chain[0] = this; 192 193 int i = 1; 194 for (Certificate c : mCertificateChain) { 195 chain[i++] = new Signature(c.getEncoded()); 196 } 197 198 return chain; 199 } 200 201 @Override 202 public boolean equals(Object obj) { 203 try { 204 if (obj != null) { 205 Signature other = (Signature)obj; 206 return this == other || Arrays.equals(mSignature, other.mSignature); 207 } 208 } catch (ClassCastException e) { 209 } 210 return false; 211 } 212 213 @Override 214 public int hashCode() { 215 if (mHaveHashCode) { 216 return mHashCode; 217 } 218 mHashCode = Arrays.hashCode(mSignature); 219 mHaveHashCode = true; 220 return mHashCode; 221 } 222 223 public int describeContents() { 224 return 0; 225 } 226 227 public void writeToParcel(Parcel dest, int parcelableFlags) { 228 dest.writeByteArray(mSignature); 229 } 230 231 public static final Parcelable.Creator<Signature> CREATOR 232 = new Parcelable.Creator<Signature>() { 233 public Signature createFromParcel(Parcel source) { 234 return new Signature(source); 235 } 236 237 public Signature[] newArray(int size) { 238 return new Signature[size]; 239 } 240 }; 241 242 private Signature(Parcel source) { 243 mSignature = source.createByteArray(); 244 } 245 246 /** 247 * Test if given {@link Signature} sets are exactly equal. 248 * 249 * @hide 250 */ 251 public static boolean areExactMatch(Signature[] a, Signature[] b) { 252 return (a.length == b.length) && ArrayUtils.containsAll(a, b) 253 && ArrayUtils.containsAll(b, a); 254 } 255 } 256