1 /* 2 * Copyright (C) 2020 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 com.android.apksig.internal.apk; 18 19 import com.android.apksig.apk.ApkFormatException; 20 import com.android.apksig.apk.ApkSigningBlockNotFoundException; 21 import com.android.apksig.apk.ApkUtilsLite; 22 import com.android.apksig.internal.util.Pair; 23 import com.android.apksig.util.DataSource; 24 import com.android.apksig.zip.ZipSections; 25 26 import java.io.IOException; 27 import java.nio.BufferUnderflowException; 28 import java.nio.ByteBuffer; 29 import java.nio.ByteOrder; 30 import java.util.ArrayList; 31 import java.util.Collections; 32 import java.util.HashMap; 33 import java.util.List; 34 import java.util.Map; 35 36 /** 37 * Lightweight version of the ApkSigningBlockUtils for clients that only require a subset of the 38 * utility functionality. 39 */ 40 public class ApkSigningBlockUtilsLite { ApkSigningBlockUtilsLite()41 private ApkSigningBlockUtilsLite() {} 42 43 private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray(); 44 /** 45 * Returns the APK Signature Scheme block contained in the provided APK file for the given ID 46 * and the additional information relevant for verifying the block against the file. 47 * 48 * @param blockId the ID value in the APK Signing Block's sequence of ID-value pairs 49 * identifying the appropriate block to find, e.g. the APK Signature Scheme v2 50 * block ID. 51 * 52 * @throws SignatureNotFoundException if the APK is not signed using given APK Signature Scheme 53 * @throws IOException if an I/O error occurs while reading the APK 54 */ findSignature( DataSource apk, ZipSections zipSections, int blockId)55 public static SignatureInfo findSignature( 56 DataSource apk, ZipSections zipSections, int blockId) 57 throws IOException, SignatureNotFoundException { 58 // Find the APK Signing Block. 59 DataSource apkSigningBlock; 60 long apkSigningBlockOffset; 61 try { 62 ApkUtilsLite.ApkSigningBlock apkSigningBlockInfo = 63 ApkUtilsLite.findApkSigningBlock(apk, zipSections); 64 apkSigningBlockOffset = apkSigningBlockInfo.getStartOffset(); 65 apkSigningBlock = apkSigningBlockInfo.getContents(); 66 } catch (ApkSigningBlockNotFoundException e) { 67 throw new SignatureNotFoundException(e.getMessage(), e); 68 } 69 ByteBuffer apkSigningBlockBuf = 70 apkSigningBlock.getByteBuffer(0, (int) apkSigningBlock.size()); 71 apkSigningBlockBuf.order(ByteOrder.LITTLE_ENDIAN); 72 73 // Find the APK Signature Scheme Block inside the APK Signing Block. 74 ByteBuffer apkSignatureSchemeBlock = 75 findApkSignatureSchemeBlock(apkSigningBlockBuf, blockId); 76 return new SignatureInfo( 77 apkSignatureSchemeBlock, 78 apkSigningBlockOffset, 79 zipSections.getZipCentralDirectoryOffset(), 80 zipSections.getZipEndOfCentralDirectoryOffset(), 81 zipSections.getZipEndOfCentralDirectory()); 82 } 83 findApkSignatureSchemeBlock( ByteBuffer apkSigningBlock, int blockId)84 public static ByteBuffer findApkSignatureSchemeBlock( 85 ByteBuffer apkSigningBlock, 86 int blockId) throws SignatureNotFoundException { 87 checkByteOrderLittleEndian(apkSigningBlock); 88 // FORMAT: 89 // OFFSET DATA TYPE DESCRIPTION 90 // * @+0 bytes uint64: size in bytes (excluding this field) 91 // * @+8 bytes pairs 92 // * @-24 bytes uint64: size in bytes (same as the one above) 93 // * @-16 bytes uint128: magic 94 ByteBuffer pairs = sliceFromTo(apkSigningBlock, 8, apkSigningBlock.capacity() - 24); 95 96 int entryCount = 0; 97 while (pairs.hasRemaining()) { 98 entryCount++; 99 if (pairs.remaining() < 8) { 100 throw new SignatureNotFoundException( 101 "Insufficient data to read size of APK Signing Block entry #" + entryCount); 102 } 103 long lenLong = pairs.getLong(); 104 if ((lenLong < 4) || (lenLong > Integer.MAX_VALUE)) { 105 throw new SignatureNotFoundException( 106 "APK Signing Block entry #" + entryCount 107 + " size out of range: " + lenLong); 108 } 109 int len = (int) lenLong; 110 int nextEntryPos = pairs.position() + len; 111 if (len > pairs.remaining()) { 112 throw new SignatureNotFoundException( 113 "APK Signing Block entry #" + entryCount + " size out of range: " + len 114 + ", available: " + pairs.remaining()); 115 } 116 int id = pairs.getInt(); 117 if (id == blockId) { 118 return getByteBuffer(pairs, len - 4); 119 } 120 pairs.position(nextEntryPos); 121 } 122 123 throw new SignatureNotFoundException( 124 "No APK Signature Scheme block in APK Signing Block with ID: " + blockId); 125 } 126 checkByteOrderLittleEndian(ByteBuffer buffer)127 public static void checkByteOrderLittleEndian(ByteBuffer buffer) { 128 if (buffer.order() != ByteOrder.LITTLE_ENDIAN) { 129 throw new IllegalArgumentException("ByteBuffer byte order must be little endian"); 130 } 131 } 132 133 /** 134 * Returns the subset of signatures which are expected to be verified by at least one Android 135 * platform version in the {@code [minSdkVersion, maxSdkVersion]} range. The returned result is 136 * guaranteed to contain at least one signature. 137 * 138 * <p>Each Android platform version typically verifies exactly one signature from the provided 139 * {@code signatures} set. This method returns the set of these signatures collected over all 140 * requested platform versions. As a result, the result may contain more than one signature. 141 * 142 * @throws NoApkSupportedSignaturesException if no supported signatures were 143 * found for an Android platform version in the range. 144 */ getSignaturesToVerify( List<T> signatures, int minSdkVersion, int maxSdkVersion)145 public static <T extends ApkSupportedSignature> List<T> getSignaturesToVerify( 146 List<T> signatures, int minSdkVersion, int maxSdkVersion) 147 throws NoApkSupportedSignaturesException { 148 return getSignaturesToVerify(signatures, minSdkVersion, maxSdkVersion, false); 149 } 150 151 /** 152 * Returns the subset of signatures which are expected to be verified by at least one Android 153 * platform version in the {@code [minSdkVersion, maxSdkVersion]} range. The returned result is 154 * guaranteed to contain at least one signature. 155 * 156 * <p>{@code onlyRequireJcaSupport} can be set to true for cases that only require verifying a 157 * signature within the signing block using the standard JCA. 158 * 159 * <p>Each Android platform version typically verifies exactly one signature from the provided 160 * {@code signatures} set. This method returns the set of these signatures collected over all 161 * requested platform versions. As a result, the result may contain more than one signature. 162 * 163 * @throws NoApkSupportedSignaturesException if no supported signatures were 164 * found for an Android platform version in the range. 165 */ getSignaturesToVerify( List<T> signatures, int minSdkVersion, int maxSdkVersion, boolean onlyRequireJcaSupport)166 public static <T extends ApkSupportedSignature> List<T> getSignaturesToVerify( 167 List<T> signatures, int minSdkVersion, int maxSdkVersion, 168 boolean onlyRequireJcaSupport) throws 169 NoApkSupportedSignaturesException { 170 // Pick the signature with the strongest algorithm at all required SDK versions, to mimic 171 // Android's behavior on those versions. 172 // 173 // Here we assume that, once introduced, a signature algorithm continues to be supported in 174 // all future Android versions. We also assume that the better-than relationship between 175 // algorithms is exactly the same on all Android platform versions (except that older 176 // platforms might support fewer algorithms). If these assumption are no longer true, the 177 // logic here will need to change accordingly. 178 Map<Integer, T> 179 bestSigAlgorithmOnSdkVersion = new HashMap<>(); 180 int minProvidedSignaturesVersion = Integer.MAX_VALUE; 181 for (T sig : signatures) { 182 SignatureAlgorithm sigAlgorithm = sig.algorithm; 183 int sigMinSdkVersion = onlyRequireJcaSupport ? sigAlgorithm.getJcaSigAlgMinSdkVersion() 184 : sigAlgorithm.getMinSdkVersion(); 185 if (sigMinSdkVersion > maxSdkVersion) { 186 continue; 187 } 188 if (sigMinSdkVersion < minProvidedSignaturesVersion) { 189 minProvidedSignaturesVersion = sigMinSdkVersion; 190 } 191 192 T candidate = bestSigAlgorithmOnSdkVersion.get(sigMinSdkVersion); 193 if ((candidate == null) 194 || (compareSignatureAlgorithm( 195 sigAlgorithm, candidate.algorithm) > 0)) { 196 bestSigAlgorithmOnSdkVersion.put(sigMinSdkVersion, sig); 197 } 198 } 199 200 // Must have some supported signature algorithms for minSdkVersion. 201 if (minSdkVersion < minProvidedSignaturesVersion) { 202 throw new NoApkSupportedSignaturesException( 203 "Minimum provided signature version " + minProvidedSignaturesVersion + 204 " > minSdkVersion " + minSdkVersion); 205 } 206 if (bestSigAlgorithmOnSdkVersion.isEmpty()) { 207 throw new NoApkSupportedSignaturesException("No supported signature"); 208 } 209 List<T> signaturesToVerify = 210 new ArrayList<>(bestSigAlgorithmOnSdkVersion.values()); 211 Collections.sort( 212 signaturesToVerify, 213 (sig1, sig2) -> Integer.compare(sig1.algorithm.getId(), sig2.algorithm.getId())); 214 return signaturesToVerify; 215 } 216 217 /** 218 * Returns positive number if {@code alg1} is preferred over {@code alg2}, {@code -1} if 219 * {@code alg2} is preferred over {@code alg1}, and {@code 0} if there is no preference. 220 */ compareSignatureAlgorithm(SignatureAlgorithm alg1, SignatureAlgorithm alg2)221 public static int compareSignatureAlgorithm(SignatureAlgorithm alg1, SignatureAlgorithm alg2) { 222 ContentDigestAlgorithm digestAlg1 = alg1.getContentDigestAlgorithm(); 223 ContentDigestAlgorithm digestAlg2 = alg2.getContentDigestAlgorithm(); 224 return compareContentDigestAlgorithm(digestAlg1, digestAlg2); 225 } 226 227 /** 228 * Returns a positive number if {@code alg1} is preferred over {@code alg2}, a negative number 229 * if {@code alg2} is preferred over {@code alg1}, or {@code 0} if there is no preference. 230 */ compareContentDigestAlgorithm( ContentDigestAlgorithm alg1, ContentDigestAlgorithm alg2)231 private static int compareContentDigestAlgorithm( 232 ContentDigestAlgorithm alg1, 233 ContentDigestAlgorithm alg2) { 234 switch (alg1) { 235 case CHUNKED_SHA256: 236 switch (alg2) { 237 case CHUNKED_SHA256: 238 return 0; 239 case CHUNKED_SHA512: 240 case VERITY_CHUNKED_SHA256: 241 return -1; 242 default: 243 throw new IllegalArgumentException("Unknown alg2: " + alg2); 244 } 245 case CHUNKED_SHA512: 246 switch (alg2) { 247 case CHUNKED_SHA256: 248 case VERITY_CHUNKED_SHA256: 249 return 1; 250 case CHUNKED_SHA512: 251 return 0; 252 default: 253 throw new IllegalArgumentException("Unknown alg2: " + alg2); 254 } 255 case VERITY_CHUNKED_SHA256: 256 switch (alg2) { 257 case CHUNKED_SHA256: 258 return 1; 259 case VERITY_CHUNKED_SHA256: 260 return 0; 261 case CHUNKED_SHA512: 262 return -1; 263 default: 264 throw new IllegalArgumentException("Unknown alg2: " + alg2); 265 } 266 default: 267 throw new IllegalArgumentException("Unknown alg1: " + alg1); 268 } 269 } 270 271 /** 272 * Returns new byte buffer whose content is a shared subsequence of this buffer's content 273 * between the specified start (inclusive) and end (exclusive) positions. As opposed to 274 * {@link ByteBuffer#slice()}, the returned buffer's byte order is the same as the source 275 * buffer's byte order. 276 */ sliceFromTo(ByteBuffer source, int start, int end)277 private static ByteBuffer sliceFromTo(ByteBuffer source, int start, int end) { 278 if (start < 0) { 279 throw new IllegalArgumentException("start: " + start); 280 } 281 if (end < start) { 282 throw new IllegalArgumentException("end < start: " + end + " < " + start); 283 } 284 int capacity = source.capacity(); 285 if (end > source.capacity()) { 286 throw new IllegalArgumentException("end > capacity: " + end + " > " + capacity); 287 } 288 int originalLimit = source.limit(); 289 int originalPosition = source.position(); 290 try { 291 source.position(0); 292 source.limit(end); 293 source.position(start); 294 ByteBuffer result = source.slice(); 295 result.order(source.order()); 296 return result; 297 } finally { 298 source.position(0); 299 source.limit(originalLimit); 300 source.position(originalPosition); 301 } 302 } 303 304 /** 305 * Relative <em>get</em> method for reading {@code size} number of bytes from the current 306 * position of this buffer. 307 * 308 * <p>This method reads the next {@code size} bytes at this buffer's current position, 309 * returning them as a {@code ByteBuffer} with start set to 0, limit and capacity set to 310 * {@code size}, byte order set to this buffer's byte order; and then increments the position by 311 * {@code size}. 312 */ getByteBuffer(ByteBuffer source, int size)313 private static ByteBuffer getByteBuffer(ByteBuffer source, int size) { 314 if (size < 0) { 315 throw new IllegalArgumentException("size: " + size); 316 } 317 int originalLimit = source.limit(); 318 int position = source.position(); 319 int limit = position + size; 320 if ((limit < position) || (limit > originalLimit)) { 321 throw new BufferUnderflowException(); 322 } 323 source.limit(limit); 324 try { 325 ByteBuffer result = source.slice(); 326 result.order(source.order()); 327 source.position(limit); 328 return result; 329 } finally { 330 source.limit(originalLimit); 331 } 332 } 333 toHex(byte[] value)334 public static String toHex(byte[] value) { 335 StringBuilder sb = new StringBuilder(value.length * 2); 336 int len = value.length; 337 for (int i = 0; i < len; i++) { 338 int hi = (value[i] & 0xff) >>> 4; 339 int lo = value[i] & 0x0f; 340 sb.append(HEX_DIGITS[hi]).append(HEX_DIGITS[lo]); 341 } 342 return sb.toString(); 343 } 344 getLengthPrefixedSlice(ByteBuffer source)345 public static ByteBuffer getLengthPrefixedSlice(ByteBuffer source) throws ApkFormatException { 346 if (source.remaining() < 4) { 347 throw new ApkFormatException( 348 "Remaining buffer too short to contain length of length-prefixed field" 349 + ". Remaining: " + source.remaining()); 350 } 351 int len = source.getInt(); 352 if (len < 0) { 353 throw new IllegalArgumentException("Negative length"); 354 } else if (len > source.remaining()) { 355 throw new ApkFormatException( 356 "Length-prefixed field longer than remaining buffer" 357 + ". Field length: " + len + ", remaining: " + source.remaining()); 358 } 359 return getByteBuffer(source, len); 360 } 361 readLengthPrefixedByteArray(ByteBuffer buf)362 public static byte[] readLengthPrefixedByteArray(ByteBuffer buf) throws ApkFormatException { 363 int len = buf.getInt(); 364 if (len < 0) { 365 throw new ApkFormatException("Negative length"); 366 } else if (len > buf.remaining()) { 367 throw new ApkFormatException( 368 "Underflow while reading length-prefixed value. Length: " + len 369 + ", available: " + buf.remaining()); 370 } 371 byte[] result = new byte[len]; 372 buf.get(result); 373 return result; 374 } 375 encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes( List<Pair<Integer, byte[]>> sequence)376 public static byte[] encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes( 377 List<Pair<Integer, byte[]>> sequence) { 378 int resultSize = 0; 379 for (Pair<Integer, byte[]> element : sequence) { 380 resultSize += 12 + element.getSecond().length; 381 } 382 ByteBuffer result = ByteBuffer.allocate(resultSize); 383 result.order(ByteOrder.LITTLE_ENDIAN); 384 for (Pair<Integer, byte[]> element : sequence) { 385 byte[] second = element.getSecond(); 386 result.putInt(8 + second.length); 387 result.putInt(element.getFirst()); 388 result.putInt(second.length); 389 result.put(second); 390 } 391 return result.array(); 392 } 393 } 394