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 android.util.apk;
18 
19 import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_SHA256;
20 import static android.util.apk.ApkSigningBlockUtils.compareSignatureAlgorithm;
21 import static android.util.apk.ApkSigningBlockUtils.getLengthPrefixedSlice;
22 import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmContentDigestAlgorithm;
23 import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmJcaSignatureAlgorithm;
24 import static android.util.apk.ApkSigningBlockUtils.isSupportedSignatureAlgorithm;
25 import static android.util.apk.ApkSigningBlockUtils.readLengthPrefixedByteArray;
26 import static android.util.apk.ApkSigningBlockUtils.verifyProofOfRotationStruct;
27 
28 import android.util.Pair;
29 import android.util.Slog;
30 import android.util.jar.StrictJarFile;
31 
32 import libcore.io.Streams;
33 
34 import java.io.ByteArrayInputStream;
35 import java.io.IOException;
36 import java.io.RandomAccessFile;
37 import java.nio.BufferUnderflowException;
38 import java.nio.ByteBuffer;
39 import java.nio.ByteOrder;
40 import java.security.InvalidAlgorithmParameterException;
41 import java.security.InvalidKeyException;
42 import java.security.MessageDigest;
43 import java.security.NoSuchAlgorithmException;
44 import java.security.PublicKey;
45 import java.security.Signature;
46 import java.security.SignatureException;
47 import java.security.cert.Certificate;
48 import java.security.cert.CertificateEncodingException;
49 import java.security.cert.CertificateException;
50 import java.security.cert.CertificateFactory;
51 import java.security.cert.X509Certificate;
52 import java.security.spec.AlgorithmParameterSpec;
53 import java.util.ArrayList;
54 import java.util.Arrays;
55 import java.util.Collections;
56 import java.util.Comparator;
57 import java.util.HashMap;
58 import java.util.List;
59 import java.util.Map;
60 import java.util.jar.JarFile;
61 import java.util.zip.ZipEntry;
62 
63 /**
64  * Source Stamp verifier.
65  *
66  * <p>SourceStamp improves traceability of apps with respect to unauthorized distribution.
67  *
68  * <p>The stamp is part of the APK that is protected by the signing block.
69  *
70  * <p>The APK contents hash is signed using the stamp key, and is saved as part of the signing
71  * block.
72  *
73  * @hide for internal use only.
74  */
75 public abstract class SourceStampVerifier {
76 
77     private static final String TAG = "SourceStampVerifier";
78 
79     private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a;
80     private static final int APK_SIGNATURE_SCHEME_V3_BLOCK_ID = 0xf05368c0;
81     private static final int SOURCE_STAMP_BLOCK_ID = 0x6dff800d;
82     private static final int PROOF_OF_ROTATION_ATTR_ID = 0x9d6303f7;
83 
84     private static final int VERSION_JAR_SIGNATURE_SCHEME = 1;
85     private static final int VERSION_APK_SIGNATURE_SCHEME_V2 = 2;
86     private static final int VERSION_APK_SIGNATURE_SCHEME_V3 = 3;
87 
88     /** Name of the SourceStamp certificate hash ZIP entry in APKs. */
89     private static final String SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME = "stamp-cert-sha256";
90 
91     /** Hidden constructor to prevent instantiation. */
SourceStampVerifier()92     private SourceStampVerifier() {
93     }
94 
95     /** Verifies SourceStamp present in a list of (split) APKs for the same app. */
verify(List<String> apkFiles)96     public static SourceStampVerificationResult verify(List<String> apkFiles) {
97         Certificate stampCertificate = null;
98         List<? extends Certificate> stampCertificateLineage = Collections.emptyList();
99         for (String apkFile : apkFiles) {
100             SourceStampVerificationResult sourceStampVerificationResult = verify(apkFile);
101             if (!sourceStampVerificationResult.isPresent()
102                     || !sourceStampVerificationResult.isVerified()) {
103                 return sourceStampVerificationResult;
104             }
105             if (stampCertificate != null
106                     && (!stampCertificate.equals(sourceStampVerificationResult.getCertificate())
107                     || !stampCertificateLineage.equals(
108                             sourceStampVerificationResult.getCertificateLineage()))) {
109                 return SourceStampVerificationResult.notVerified();
110             }
111             stampCertificate = sourceStampVerificationResult.getCertificate();
112             stampCertificateLineage = sourceStampVerificationResult.getCertificateLineage();
113         }
114         return SourceStampVerificationResult.verified(stampCertificate, stampCertificateLineage);
115     }
116 
117     /** Verifies SourceStamp present in the provided APK. */
verify(String apkFile)118     public static SourceStampVerificationResult verify(String apkFile) {
119         StrictJarFile apkJar = null;
120         try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) {
121             apkJar =
122                     new StrictJarFile(
123                             apkFile,
124                             /* verify= */ false,
125                             /* signatureSchemeRollbackProtectionsEnforced= */ false);
126             byte[] sourceStampCertificateDigest = getSourceStampCertificateDigest(apkJar);
127             if (sourceStampCertificateDigest == null) {
128                 // SourceStamp certificate hash file not found, which means that there is not
129                 // SourceStamp present.
130                 return SourceStampVerificationResult.notPresent();
131             }
132             byte[] manifestBytes = getManifestBytes(apkJar);
133             return verify(apk, sourceStampCertificateDigest, manifestBytes);
134         } catch (IOException e) {
135             // Any exception in reading the APK returns a non-present SourceStamp outcome
136             // without affecting the outcome of any of the other signature schemes.
137             return SourceStampVerificationResult.notPresent();
138         } finally {
139             closeApkJar(apkJar);
140         }
141     }
142 
verify( RandomAccessFile apk, byte[] sourceStampCertificateDigest, byte[] manifestBytes)143     private static SourceStampVerificationResult verify(
144             RandomAccessFile apk, byte[] sourceStampCertificateDigest, byte[] manifestBytes) {
145         try {
146             SignatureInfo signatureInfo =
147                     ApkSigningBlockUtils.findSignature(apk, SOURCE_STAMP_BLOCK_ID);
148             Map<Integer, Map<Integer, byte[]>> signatureSchemeApkContentDigests =
149                     getSignatureSchemeApkContentDigests(apk, manifestBytes);
150             return verify(
151                     signatureInfo,
152                     getSignatureSchemeDigests(signatureSchemeApkContentDigests),
153                     sourceStampCertificateDigest);
154         } catch (IOException | SignatureNotFoundException | RuntimeException e) {
155             return SourceStampVerificationResult.notVerified();
156         }
157     }
158 
verify( SignatureInfo signatureInfo, Map<Integer, byte[]> signatureSchemeDigests, byte[] sourceStampCertificateDigest)159     private static SourceStampVerificationResult verify(
160             SignatureInfo signatureInfo,
161             Map<Integer, byte[]> signatureSchemeDigests,
162             byte[] sourceStampCertificateDigest)
163             throws SecurityException, IOException {
164         ByteBuffer sourceStampBlock = signatureInfo.signatureBlock;
165         ByteBuffer sourceStampBlockData =
166                 ApkSigningBlockUtils.getLengthPrefixedSlice(sourceStampBlock);
167 
168         X509Certificate sourceStampCertificate =
169                 verifySourceStampCertificate(sourceStampBlockData, sourceStampCertificateDigest);
170 
171         // Parse signed signature schemes block.
172         ByteBuffer signedSignatureSchemes =
173                 ApkSigningBlockUtils.getLengthPrefixedSlice(sourceStampBlockData);
174         Map<Integer, ByteBuffer> signedSignatureSchemeData = new HashMap<>();
175         while (signedSignatureSchemes.hasRemaining()) {
176             ByteBuffer signedSignatureScheme =
177                     ApkSigningBlockUtils.getLengthPrefixedSlice(signedSignatureSchemes);
178             int signatureSchemeId = signedSignatureScheme.getInt();
179             signedSignatureSchemeData.put(signatureSchemeId, signedSignatureScheme);
180         }
181 
182         for (Map.Entry<Integer, byte[]> signatureSchemeDigest : signatureSchemeDigests.entrySet()) {
183             if (!signedSignatureSchemeData.containsKey(signatureSchemeDigest.getKey())) {
184                 throw new SecurityException(
185                         String.format(
186                                 "No signatures found for signature scheme %d",
187                                 signatureSchemeDigest.getKey()));
188             }
189             ByteBuffer signatures = ApkSigningBlockUtils.getLengthPrefixedSlice(
190                     signedSignatureSchemeData.get(signatureSchemeDigest.getKey()));
191             verifySourceStampSignature(
192                     signatureSchemeDigest.getValue(),
193                     sourceStampCertificate,
194                     signatures);
195         }
196 
197         List<? extends Certificate> sourceStampCertificateLineage = Collections.emptyList();
198         if (sourceStampBlockData.hasRemaining()) {
199             // The stamp block contains some additional attributes.
200             ByteBuffer stampAttributeData = getLengthPrefixedSlice(sourceStampBlockData);
201             ByteBuffer stampAttributeDataSignatures = getLengthPrefixedSlice(sourceStampBlockData);
202 
203             byte[] stampAttributeBytes = new byte[stampAttributeData.remaining()];
204             stampAttributeData.get(stampAttributeBytes);
205             stampAttributeData.flip();
206 
207             verifySourceStampSignature(stampAttributeBytes, sourceStampCertificate,
208                     stampAttributeDataSignatures);
209             ApkSigningBlockUtils.VerifiedProofOfRotation verifiedProofOfRotation =
210                     verifySourceStampAttributes(stampAttributeData, sourceStampCertificate);
211             if (verifiedProofOfRotation != null) {
212                 sourceStampCertificateLineage = verifiedProofOfRotation.certs;
213             }
214         }
215 
216         return SourceStampVerificationResult.verified(sourceStampCertificate,
217                 sourceStampCertificateLineage);
218     }
219 
220     /**
221      * Verify the SourceStamp certificate found in the signing block is the same as the SourceStamp
222      * certificate found in the APK. It returns the verified certificate.
223      *
224      * @param sourceStampBlockData         the source stamp block in the APK signing block which
225      *                                     contains
226      *                                     the certificate used to sign the stamp digests.
227      * @param sourceStampCertificateDigest the source stamp certificate digest found in the APK.
228      */
verifySourceStampCertificate( ByteBuffer sourceStampBlockData, byte[] sourceStampCertificateDigest)229     private static X509Certificate verifySourceStampCertificate(
230             ByteBuffer sourceStampBlockData, byte[] sourceStampCertificateDigest)
231             throws IOException {
232         CertificateFactory certFactory;
233         try {
234             certFactory = CertificateFactory.getInstance("X.509");
235         } catch (CertificateException e) {
236             throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
237         }
238 
239         // Parse the SourceStamp certificate.
240         byte[] sourceStampEncodedCertificate =
241                 ApkSigningBlockUtils.readLengthPrefixedByteArray(sourceStampBlockData);
242         X509Certificate sourceStampCertificate;
243         try {
244             sourceStampCertificate =
245                     (X509Certificate)
246                             certFactory.generateCertificate(
247                                     new ByteArrayInputStream(sourceStampEncodedCertificate));
248         } catch (CertificateException e) {
249             throw new SecurityException("Failed to decode certificate", e);
250         }
251 
252         byte[] sourceStampBlockCertificateDigest =
253                 computeSha256Digest(sourceStampEncodedCertificate);
254         if (!Arrays.equals(sourceStampCertificateDigest, sourceStampBlockCertificateDigest)) {
255             throw new SecurityException("Certificate mismatch between APK and signature block");
256         }
257 
258         return new VerbatimX509Certificate(sourceStampCertificate, sourceStampEncodedCertificate);
259     }
260 
261     /**
262      * Verify the SourceStamp signature found in the signing block is signed by the SourceStamp
263      * certificate found in the APK.
264      *
265      * @param data                   the digest to be verified being signed by the source stamp
266      *                               certificate.
267      * @param sourceStampCertificate the source stamp certificate used to sign the stamp digests.
268      * @param signatures             the source stamp block in the APK signing block which contains
269      *                               the stamp signed digests.
270      */
verifySourceStampSignature(byte[] data, X509Certificate sourceStampCertificate, ByteBuffer signatures)271     private static void verifySourceStampSignature(byte[] data,
272             X509Certificate sourceStampCertificate, ByteBuffer signatures)
273             throws IOException {
274         // Parse the signatures block and identify supported signatures
275         int signatureCount = 0;
276         int bestSigAlgorithm = -1;
277         byte[] bestSigAlgorithmSignatureBytes = null;
278         while (signatures.hasRemaining()) {
279             signatureCount++;
280             try {
281                 ByteBuffer signature = getLengthPrefixedSlice(signatures);
282                 if (signature.remaining() < 8) {
283                     throw new SecurityException("Signature record too short");
284                 }
285                 int sigAlgorithm = signature.getInt();
286                 if (!isSupportedSignatureAlgorithm(sigAlgorithm)) {
287                     continue;
288                 }
289                 if ((bestSigAlgorithm == -1)
290                         || (compareSignatureAlgorithm(sigAlgorithm, bestSigAlgorithm) > 0)) {
291                     bestSigAlgorithm = sigAlgorithm;
292                     bestSigAlgorithmSignatureBytes = readLengthPrefixedByteArray(signature);
293                 }
294             } catch (IOException | BufferUnderflowException e) {
295                 throw new SecurityException(
296                         "Failed to parse signature record #" + signatureCount, e);
297             }
298         }
299         if (bestSigAlgorithm == -1) {
300             if (signatureCount == 0) {
301                 throw new SecurityException("No signatures found");
302             } else {
303                 throw new SecurityException("No supported signatures found");
304             }
305         }
306 
307         // Verify signatures over digests using the SourceStamp's certificate.
308         Pair<String, ? extends AlgorithmParameterSpec> signatureAlgorithmParams =
309                 getSignatureAlgorithmJcaSignatureAlgorithm(bestSigAlgorithm);
310         String jcaSignatureAlgorithm = signatureAlgorithmParams.first;
311         AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureAlgorithmParams.second;
312         PublicKey publicKey = sourceStampCertificate.getPublicKey();
313         boolean sigVerified;
314         try {
315             Signature sig = Signature.getInstance(jcaSignatureAlgorithm);
316             sig.initVerify(publicKey);
317             if (jcaSignatureAlgorithmParams != null) {
318                 sig.setParameter(jcaSignatureAlgorithmParams);
319             }
320             sig.update(data);
321             sigVerified = sig.verify(bestSigAlgorithmSignatureBytes);
322         } catch (InvalidKeyException
323                 | InvalidAlgorithmParameterException
324                 | SignatureException
325                 | NoSuchAlgorithmException e) {
326             throw new SecurityException(
327                     "Failed to verify " + jcaSignatureAlgorithm + " signature", e);
328         }
329         if (!sigVerified) {
330             throw new SecurityException(jcaSignatureAlgorithm + " signature did not verify");
331         }
332     }
333 
getSignatureSchemeApkContentDigests( RandomAccessFile apk, byte[] manifestBytes)334     private static Map<Integer, Map<Integer, byte[]>> getSignatureSchemeApkContentDigests(
335             RandomAccessFile apk, byte[] manifestBytes) throws IOException {
336         Map<Integer, Map<Integer, byte[]>> signatureSchemeApkContentDigests = new HashMap<>();
337 
338         // Retrieve APK content digests in V3 signing block.
339         try {
340             SignatureInfo v3SignatureInfo =
341                     ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V3_BLOCK_ID);
342             signatureSchemeApkContentDigests.put(
343                     VERSION_APK_SIGNATURE_SCHEME_V3,
344                     getApkContentDigestsFromSignatureBlock(v3SignatureInfo.signatureBlock));
345         } catch (SignatureNotFoundException e) {
346             // It's fine not to find a V3 signature.
347         }
348 
349         // Retrieve APK content digests in V2 signing block.
350         try {
351             SignatureInfo v2SignatureInfo =
352                     ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V2_BLOCK_ID);
353             signatureSchemeApkContentDigests.put(
354                     VERSION_APK_SIGNATURE_SCHEME_V2,
355                     getApkContentDigestsFromSignatureBlock(v2SignatureInfo.signatureBlock));
356         } catch (SignatureNotFoundException e) {
357             // It's fine not to find a V2 signature.
358         }
359 
360         // Retrieve manifest digest.
361         if (manifestBytes != null) {
362             Map<Integer, byte[]> jarSignatureSchemeApkContentDigests = new HashMap<>();
363             jarSignatureSchemeApkContentDigests.put(
364                     CONTENT_DIGEST_SHA256, computeSha256Digest(manifestBytes));
365             signatureSchemeApkContentDigests.put(
366                     VERSION_JAR_SIGNATURE_SCHEME, jarSignatureSchemeApkContentDigests);
367         }
368 
369         return signatureSchemeApkContentDigests;
370     }
371 
getApkContentDigestsFromSignatureBlock( ByteBuffer signatureBlock)372     private static Map<Integer, byte[]> getApkContentDigestsFromSignatureBlock(
373             ByteBuffer signatureBlock) throws IOException {
374         Map<Integer, byte[]> apkContentDigests = new HashMap<>();
375         ByteBuffer signers = getLengthPrefixedSlice(signatureBlock);
376         while (signers.hasRemaining()) {
377             ByteBuffer signer = getLengthPrefixedSlice(signers);
378             ByteBuffer signedData = getLengthPrefixedSlice(signer);
379             ByteBuffer digests = getLengthPrefixedSlice(signedData);
380             while (digests.hasRemaining()) {
381                 ByteBuffer digest = getLengthPrefixedSlice(digests);
382                 int sigAlgorithm = digest.getInt();
383                 byte[] contentDigest = readLengthPrefixedByteArray(digest);
384                 int digestAlgorithm = getSignatureAlgorithmContentDigestAlgorithm(sigAlgorithm);
385                 apkContentDigests.put(digestAlgorithm, contentDigest);
386             }
387         }
388         return apkContentDigests;
389     }
390 
getSignatureSchemeDigests( Map<Integer, Map<Integer, byte[]>> signatureSchemeApkContentDigests)391     private static Map<Integer, byte[]> getSignatureSchemeDigests(
392             Map<Integer, Map<Integer, byte[]>> signatureSchemeApkContentDigests) {
393         Map<Integer, byte[]> digests = new HashMap<>();
394         for (Map.Entry<Integer, Map<Integer, byte[]>> signatureSchemeApkContentDigest :
395                 signatureSchemeApkContentDigests.entrySet()) {
396             List<Pair<Integer, byte[]>> apkDigests =
397                     getApkDigests(signatureSchemeApkContentDigest.getValue());
398             digests.put(
399                     signatureSchemeApkContentDigest.getKey(), encodeApkContentDigests(apkDigests));
400         }
401         return digests;
402     }
403 
getApkDigests( Map<Integer, byte[]> apkContentDigests)404     private static List<Pair<Integer, byte[]>> getApkDigests(
405             Map<Integer, byte[]> apkContentDigests) {
406         List<Pair<Integer, byte[]>> digests = new ArrayList<>();
407         for (Map.Entry<Integer, byte[]> apkContentDigest : apkContentDigests.entrySet()) {
408             digests.add(Pair.create(apkContentDigest.getKey(), apkContentDigest.getValue()));
409         }
410         digests.sort(Comparator.comparing(pair -> pair.first));
411         return digests;
412     }
413 
getSourceStampCertificateDigest(StrictJarFile apkJar)414     private static byte[] getSourceStampCertificateDigest(StrictJarFile apkJar) throws IOException {
415         ZipEntry zipEntry = apkJar.findEntry(SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME);
416         if (zipEntry == null) {
417             // SourceStamp certificate hash file not found, which means that there is not
418             // SourceStamp present.
419             return null;
420         }
421         return Streams.readFully(apkJar.getInputStream(zipEntry));
422     }
423 
getManifestBytes(StrictJarFile apkJar)424     private static byte[] getManifestBytes(StrictJarFile apkJar) throws IOException {
425         ZipEntry zipEntry = apkJar.findEntry(JarFile.MANIFEST_NAME);
426         if (zipEntry == null) {
427             return null;
428         }
429         return Streams.readFully(apkJar.getInputStream(zipEntry));
430     }
431 
encodeApkContentDigests(List<Pair<Integer, byte[]>> apkContentDigests)432     private static byte[] encodeApkContentDigests(List<Pair<Integer, byte[]>> apkContentDigests) {
433         int resultSize = 0;
434         for (Pair<Integer, byte[]> element : apkContentDigests) {
435             resultSize += 12 + element.second.length;
436         }
437         ByteBuffer result = ByteBuffer.allocate(resultSize);
438         result.order(ByteOrder.LITTLE_ENDIAN);
439         for (Pair<Integer, byte[]> element : apkContentDigests) {
440             byte[] second = element.second;
441             result.putInt(8 + second.length);
442             result.putInt(element.first);
443             result.putInt(second.length);
444             result.put(second);
445         }
446         return result.array();
447     }
448 
verifySourceStampAttributes( ByteBuffer stampAttributeData, X509Certificate sourceStampCertificate)449     private static ApkSigningBlockUtils.VerifiedProofOfRotation verifySourceStampAttributes(
450             ByteBuffer stampAttributeData,
451             X509Certificate sourceStampCertificate)
452             throws IOException {
453         CertificateFactory certFactory;
454         try {
455             certFactory = CertificateFactory.getInstance("X.509");
456         } catch (CertificateException e) {
457             throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
458         }
459         ByteBuffer stampAttributes = getLengthPrefixedSlice(stampAttributeData);
460         ApkSigningBlockUtils.VerifiedProofOfRotation verifiedProofOfRotation = null;
461         while (stampAttributes.hasRemaining()) {
462             ByteBuffer attribute = getLengthPrefixedSlice(stampAttributes);
463             int id = attribute.getInt();
464             if (id == PROOF_OF_ROTATION_ATTR_ID) {
465                 if (verifiedProofOfRotation != null) {
466                     throw new SecurityException("Encountered multiple Proof-of-rotation records"
467                             + " when verifying source stamp signature");
468                 }
469                 verifiedProofOfRotation = verifyProofOfRotationStruct(attribute, certFactory);
470                 // Make sure that the last certificate in the Proof-of-rotation record matches
471                 // the one used to sign this APK.
472                 try {
473                     if (verifiedProofOfRotation.certs.size() > 0
474                             && !Arrays.equals(verifiedProofOfRotation.certs.get(
475                             verifiedProofOfRotation.certs.size() - 1).getEncoded(),
476                             sourceStampCertificate.getEncoded())) {
477                         throw new SecurityException("Terminal certificate in Proof-of-rotation"
478                                 + " record does not match source stamp certificate");
479                     }
480                 } catch (CertificateEncodingException e) {
481                     throw new SecurityException("Failed to encode certificate when comparing"
482                             + " Proof-of-rotation record and source stamp certificate", e);
483                 }
484             }
485         }
486         return verifiedProofOfRotation;
487     }
488 
computeSha256Digest(byte[] input)489     private static byte[] computeSha256Digest(byte[] input) {
490         try {
491             MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
492             messageDigest.update(input);
493             return messageDigest.digest();
494         } catch (NoSuchAlgorithmException e) {
495             throw new RuntimeException("Failed to find SHA-256", e);
496         }
497     }
498 
closeApkJar(StrictJarFile apkJar)499     private static void closeApkJar(StrictJarFile apkJar) {
500         try {
501             if (apkJar == null) {
502                 return;
503             }
504             apkJar.close();
505         } catch (IOException e) {
506             Slog.e(TAG, "Could not close APK jar", e);
507         }
508     }
509 }
510