• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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