1 /*
2  * Copyright (C) 2016 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;
18 
19 import static com.android.apksig.apk.ApkUtils.SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME;
20 
21 import com.android.apksig.apk.ApkFormatException;
22 import com.android.apksig.apk.ApkSigningBlockNotFoundException;
23 import com.android.apksig.apk.ApkUtils;
24 import com.android.apksig.apk.MinSdkVersionException;
25 import com.android.apksig.internal.util.ByteBufferDataSource;
26 import com.android.apksig.internal.zip.CentralDirectoryRecord;
27 import com.android.apksig.internal.zip.EocdRecord;
28 import com.android.apksig.internal.zip.LocalFileRecord;
29 import com.android.apksig.internal.zip.ZipUtils;
30 import com.android.apksig.util.DataSink;
31 import com.android.apksig.util.DataSinks;
32 import com.android.apksig.util.DataSource;
33 import com.android.apksig.util.DataSources;
34 import com.android.apksig.util.ReadableDataSink;
35 import com.android.apksig.zip.ZipFormatException;
36 
37 import java.io.Closeable;
38 import java.io.File;
39 import java.io.IOException;
40 import java.io.RandomAccessFile;
41 import java.nio.ByteBuffer;
42 import java.nio.ByteOrder;
43 import java.security.InvalidKeyException;
44 import java.security.NoSuchAlgorithmException;
45 import java.security.PrivateKey;
46 import java.security.SignatureException;
47 import java.security.cert.X509Certificate;
48 import java.util.ArrayList;
49 import java.util.Arrays;
50 import java.util.Collections;
51 import java.util.HashMap;
52 import java.util.HashSet;
53 import java.util.List;
54 import java.util.Map;
55 import java.util.Set;
56 
57 /**
58  * APK signer.
59  *
60  * <p>The signer preserves as much of the input APK as possible. For example, it preserves the order
61  * of APK entries and preserves their contents, including compressed form and alignment of data.
62  *
63  * <p>Use {@link Builder} to obtain instances of this signer.
64  *
65  * @see <a href="https://source.android.com/security/apksigning/index.html">Application Signing</a>
66  */
67 public class ApkSigner {
68 
69     /**
70      * Extensible data block/field header ID used for storing information about alignment of
71      * uncompressed entries as well as for aligning the entries's data. See ZIP appnote.txt section
72      * 4.5 Extensible data fields.
73      */
74     private static final short ALIGNMENT_ZIP_EXTRA_DATA_FIELD_HEADER_ID = (short) 0xd935;
75 
76     /**
77      * Minimum size (in bytes) of the extensible data block/field used for alignment of uncompressed
78      * entries.
79      */
80     private static final short ALIGNMENT_ZIP_EXTRA_DATA_FIELD_MIN_SIZE_BYTES = 6;
81 
82     private static final short ANDROID_COMMON_PAGE_ALIGNMENT_BYTES = 4096;
83 
84     /** Name of the Android manifest ZIP entry in APKs. */
85     private static final String ANDROID_MANIFEST_ZIP_ENTRY_NAME = "AndroidManifest.xml";
86 
87     private final List<SignerConfig> mSignerConfigs;
88     private final SignerConfig mSourceStampSignerConfig;
89     private final boolean mForceSourceStampOverwrite;
90     private final Integer mMinSdkVersion;
91     private final boolean mV1SigningEnabled;
92     private final boolean mV2SigningEnabled;
93     private final boolean mV3SigningEnabled;
94     private final boolean mV4SigningEnabled;
95     private final boolean mVerityEnabled;
96     private final boolean mV4ErrorReportingEnabled;
97     private final boolean mDebuggableApkPermitted;
98     private final boolean mOtherSignersSignaturesPreserved;
99     private final String mCreatedBy;
100 
101     private final ApkSignerEngine mSignerEngine;
102 
103     private final File mInputApkFile;
104     private final DataSource mInputApkDataSource;
105 
106     private final File mOutputApkFile;
107     private final DataSink mOutputApkDataSink;
108     private final DataSource mOutputApkDataSource;
109 
110     private final File mOutputV4File;
111 
112     private final SigningCertificateLineage mSigningCertificateLineage;
113 
ApkSigner( List<SignerConfig> signerConfigs, SignerConfig sourceStampSignerConfig, boolean forceSourceStampOverwrite, Integer minSdkVersion, boolean v1SigningEnabled, boolean v2SigningEnabled, boolean v3SigningEnabled, boolean v4SigningEnabled, boolean verityEnabled, boolean v4ErrorReportingEnabled, boolean debuggableApkPermitted, boolean otherSignersSignaturesPreserved, String createdBy, ApkSignerEngine signerEngine, File inputApkFile, DataSource inputApkDataSource, File outputApkFile, DataSink outputApkDataSink, DataSource outputApkDataSource, File outputV4File, SigningCertificateLineage signingCertificateLineage)114     private ApkSigner(
115             List<SignerConfig> signerConfigs,
116             SignerConfig sourceStampSignerConfig,
117             boolean forceSourceStampOverwrite,
118             Integer minSdkVersion,
119             boolean v1SigningEnabled,
120             boolean v2SigningEnabled,
121             boolean v3SigningEnabled,
122             boolean v4SigningEnabled,
123             boolean verityEnabled,
124             boolean v4ErrorReportingEnabled,
125             boolean debuggableApkPermitted,
126             boolean otherSignersSignaturesPreserved,
127             String createdBy,
128             ApkSignerEngine signerEngine,
129             File inputApkFile,
130             DataSource inputApkDataSource,
131             File outputApkFile,
132             DataSink outputApkDataSink,
133             DataSource outputApkDataSource,
134             File outputV4File,
135             SigningCertificateLineage signingCertificateLineage) {
136 
137         mSignerConfigs = signerConfigs;
138         mSourceStampSignerConfig = sourceStampSignerConfig;
139         mForceSourceStampOverwrite = forceSourceStampOverwrite;
140         mMinSdkVersion = minSdkVersion;
141         mV1SigningEnabled = v1SigningEnabled;
142         mV2SigningEnabled = v2SigningEnabled;
143         mV3SigningEnabled = v3SigningEnabled;
144         mV4SigningEnabled = v4SigningEnabled;
145         mVerityEnabled = verityEnabled;
146         mV4ErrorReportingEnabled = v4ErrorReportingEnabled;
147         mDebuggableApkPermitted = debuggableApkPermitted;
148         mOtherSignersSignaturesPreserved = otherSignersSignaturesPreserved;
149         mCreatedBy = createdBy;
150 
151         mSignerEngine = signerEngine;
152 
153         mInputApkFile = inputApkFile;
154         mInputApkDataSource = inputApkDataSource;
155 
156         mOutputApkFile = outputApkFile;
157         mOutputApkDataSink = outputApkDataSink;
158         mOutputApkDataSource = outputApkDataSource;
159 
160         mOutputV4File = outputV4File;
161 
162         mSigningCertificateLineage = signingCertificateLineage;
163     }
164 
165     /**
166      * Signs the input APK and outputs the resulting signed APK. The input APK is not modified.
167      *
168      * @throws IOException if an I/O error is encountered while reading or writing the APKs
169      * @throws ApkFormatException if the input APK is malformed
170      * @throws NoSuchAlgorithmException if the APK signatures cannot be produced or verified because
171      *     a required cryptographic algorithm implementation is missing
172      * @throws InvalidKeyException if a signature could not be generated because a signing key is
173      *     not suitable for generating the signature
174      * @throws SignatureException if an error occurred while generating or verifying a signature
175      * @throws IllegalStateException if this signer's configuration is missing required information
176      *     or if the signing engine is in an invalid state.
177      */
sign()178     public void sign()
179             throws IOException, ApkFormatException, NoSuchAlgorithmException, InvalidKeyException,
180                     SignatureException, IllegalStateException {
181         Closeable in = null;
182         DataSource inputApk;
183         try {
184             if (mInputApkDataSource != null) {
185                 inputApk = mInputApkDataSource;
186             } else if (mInputApkFile != null) {
187                 RandomAccessFile inputFile = new RandomAccessFile(mInputApkFile, "r");
188                 in = inputFile;
189                 inputApk = DataSources.asDataSource(inputFile);
190             } else {
191                 throw new IllegalStateException("Input APK not specified");
192             }
193 
194             Closeable out = null;
195             try {
196                 DataSink outputApkOut;
197                 DataSource outputApkIn;
198                 if (mOutputApkDataSink != null) {
199                     outputApkOut = mOutputApkDataSink;
200                     outputApkIn = mOutputApkDataSource;
201                 } else if (mOutputApkFile != null) {
202                     RandomAccessFile outputFile = new RandomAccessFile(mOutputApkFile, "rw");
203                     out = outputFile;
204                     outputFile.setLength(0);
205                     outputApkOut = DataSinks.asDataSink(outputFile);
206                     outputApkIn = DataSources.asDataSource(outputFile);
207                 } else {
208                     throw new IllegalStateException("Output APK not specified");
209                 }
210 
211                 sign(inputApk, outputApkOut, outputApkIn);
212             } finally {
213                 if (out != null) {
214                     out.close();
215                 }
216             }
217         } finally {
218             if (in != null) {
219                 in.close();
220             }
221         }
222     }
223 
sign(DataSource inputApk, DataSink outputApkOut, DataSource outputApkIn)224     private void sign(DataSource inputApk, DataSink outputApkOut, DataSource outputApkIn)
225             throws IOException, ApkFormatException, NoSuchAlgorithmException, InvalidKeyException,
226                     SignatureException {
227         // Step 1. Find input APK's main ZIP sections
228         ApkUtils.ZipSections inputZipSections;
229         try {
230             inputZipSections = ApkUtils.findZipSections(inputApk);
231         } catch (ZipFormatException e) {
232             throw new ApkFormatException("Malformed APK: not a ZIP archive", e);
233         }
234         long inputApkSigningBlockOffset = -1;
235         DataSource inputApkSigningBlock = null;
236         try {
237             ApkUtils.ApkSigningBlock apkSigningBlockInfo =
238                     ApkUtils.findApkSigningBlock(inputApk, inputZipSections);
239             inputApkSigningBlockOffset = apkSigningBlockInfo.getStartOffset();
240             inputApkSigningBlock = apkSigningBlockInfo.getContents();
241         } catch (ApkSigningBlockNotFoundException e) {
242             // Input APK does not contain an APK Signing Block. That's OK. APKs are not required to
243             // contain this block. It's only needed if the APK is signed using APK Signature Scheme
244             // v2 and/or v3.
245         }
246         DataSource inputApkLfhSection =
247                 inputApk.slice(
248                         0,
249                         (inputApkSigningBlockOffset != -1)
250                                 ? inputApkSigningBlockOffset
251                                 : inputZipSections.getZipCentralDirectoryOffset());
252 
253         // Step 2. Parse the input APK's ZIP Central Directory
254         ByteBuffer inputCd = getZipCentralDirectory(inputApk, inputZipSections);
255         List<CentralDirectoryRecord> inputCdRecords =
256                 parseZipCentralDirectory(inputCd, inputZipSections);
257 
258         List<Hints.PatternWithRange> pinPatterns =
259                 extractPinPatterns(inputCdRecords, inputApkLfhSection);
260         List<Hints.ByteRange> pinByteRanges = pinPatterns == null ? null : new ArrayList<>();
261 
262         // Step 3. Obtain a signer engine instance
263         ApkSignerEngine signerEngine;
264         if (mSignerEngine != null) {
265             // Use the provided signer engine
266             signerEngine = mSignerEngine;
267         } else {
268             // Construct a signer engine from the provided parameters
269             int minSdkVersion;
270             if (mMinSdkVersion != null) {
271                 // No need to extract minSdkVersion from the APK's AndroidManifest.xml
272                 minSdkVersion = mMinSdkVersion;
273             } else {
274                 // Need to extract minSdkVersion from the APK's AndroidManifest.xml
275                 minSdkVersion = getMinSdkVersionFromApk(inputCdRecords, inputApkLfhSection);
276             }
277             List<DefaultApkSignerEngine.SignerConfig> engineSignerConfigs =
278                     new ArrayList<>(mSignerConfigs.size());
279             for (SignerConfig signerConfig : mSignerConfigs) {
280                 engineSignerConfigs.add(
281                         new DefaultApkSignerEngine.SignerConfig.Builder(
282                                         signerConfig.getName(),
283                                         signerConfig.getPrivateKey(),
284                                         signerConfig.getCertificates())
285                                 .build());
286             }
287             DefaultApkSignerEngine.Builder signerEngineBuilder =
288                     new DefaultApkSignerEngine.Builder(engineSignerConfigs, minSdkVersion)
289                             .setV1SigningEnabled(mV1SigningEnabled)
290                             .setV2SigningEnabled(mV2SigningEnabled)
291                             .setV3SigningEnabled(mV3SigningEnabled)
292                             .setVerityEnabled(mVerityEnabled)
293                             .setDebuggableApkPermitted(mDebuggableApkPermitted)
294                             .setOtherSignersSignaturesPreserved(mOtherSignersSignaturesPreserved)
295                             .setSigningCertificateLineage(mSigningCertificateLineage);
296             if (mCreatedBy != null) {
297                 signerEngineBuilder.setCreatedBy(mCreatedBy);
298             }
299             if (mSourceStampSignerConfig != null) {
300                 signerEngineBuilder.setStampSignerConfig(
301                         new DefaultApkSignerEngine.SignerConfig.Builder(
302                                         mSourceStampSignerConfig.getName(),
303                                         mSourceStampSignerConfig.getPrivateKey(),
304                                         mSourceStampSignerConfig.getCertificates())
305                                 .build());
306             }
307             signerEngine = signerEngineBuilder.build();
308         }
309 
310         // Step 4. Provide the signer engine with the input APK's APK Signing Block (if any)
311         if (inputApkSigningBlock != null) {
312             signerEngine.inputApkSigningBlock(inputApkSigningBlock);
313         }
314 
315         // Step 5. Iterate over input APK's entries and output the Local File Header + data of those
316         // entries which need to be output. Entries are iterated in the order in which their Local
317         // File Header records are stored in the file. This is to achieve better data locality in
318         // case Central Directory entries are in the wrong order.
319         List<CentralDirectoryRecord> inputCdRecordsSortedByLfhOffset =
320                 new ArrayList<>(inputCdRecords);
321         Collections.sort(
322                 inputCdRecordsSortedByLfhOffset,
323                 CentralDirectoryRecord.BY_LOCAL_FILE_HEADER_OFFSET_COMPARATOR);
324         int lastModifiedDateForNewEntries = -1;
325         int lastModifiedTimeForNewEntries = -1;
326         long inputOffset = 0;
327         long outputOffset = 0;
328         byte[] sourceStampCertificateDigest = null;
329         Map<String, CentralDirectoryRecord> outputCdRecordsByName =
330                 new HashMap<>(inputCdRecords.size());
331         for (final CentralDirectoryRecord inputCdRecord : inputCdRecordsSortedByLfhOffset) {
332             String entryName = inputCdRecord.getName();
333             if (Hints.PIN_BYTE_RANGE_ZIP_ENTRY_NAME.equals(entryName)) {
334                 continue; // We'll re-add below if needed.
335             }
336             if (SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME.equals(entryName)) {
337                 try {
338                     sourceStampCertificateDigest =
339                             LocalFileRecord.getUncompressedData(
340                                     inputApkLfhSection, inputCdRecord, inputApkLfhSection.size());
341                 } catch (ZipFormatException ex) {
342                     throw new ApkFormatException("Bad source stamp entry");
343                 }
344                 continue; // Existing source stamp is handled below as needed.
345             }
346             ApkSignerEngine.InputJarEntryInstructions entryInstructions =
347                     signerEngine.inputJarEntry(entryName);
348             boolean shouldOutput;
349             switch (entryInstructions.getOutputPolicy()) {
350                 case OUTPUT:
351                     shouldOutput = true;
352                     break;
353                 case OUTPUT_BY_ENGINE:
354                 case SKIP:
355                     shouldOutput = false;
356                     break;
357                 default:
358                     throw new RuntimeException(
359                             "Unknown output policy: " + entryInstructions.getOutputPolicy());
360             }
361 
362             long inputLocalFileHeaderStartOffset = inputCdRecord.getLocalFileHeaderOffset();
363             if (inputLocalFileHeaderStartOffset > inputOffset) {
364                 // Unprocessed data in input starting at inputOffset and ending and the start of
365                 // this record's LFH. We output this data verbatim because this signer is supposed
366                 // to preserve as much of input as possible.
367                 long chunkSize = inputLocalFileHeaderStartOffset - inputOffset;
368                 inputApkLfhSection.feed(inputOffset, chunkSize, outputApkOut);
369                 outputOffset += chunkSize;
370                 inputOffset = inputLocalFileHeaderStartOffset;
371             }
372             LocalFileRecord inputLocalFileRecord;
373             try {
374                 inputLocalFileRecord =
375                         LocalFileRecord.getRecord(
376                                 inputApkLfhSection, inputCdRecord, inputApkLfhSection.size());
377             } catch (ZipFormatException e) {
378                 throw new ApkFormatException("Malformed ZIP entry: " + inputCdRecord.getName(), e);
379             }
380             inputOffset += inputLocalFileRecord.getSize();
381 
382             ApkSignerEngine.InspectJarEntryRequest inspectEntryRequest =
383                     entryInstructions.getInspectJarEntryRequest();
384             if (inspectEntryRequest != null) {
385                 fulfillInspectInputJarEntryRequest(
386                         inputApkLfhSection, inputLocalFileRecord, inspectEntryRequest);
387             }
388 
389             if (shouldOutput) {
390                 // Find the max value of last modified, to be used for new entries added by the
391                 // signer.
392                 int lastModifiedDate = inputCdRecord.getLastModificationDate();
393                 int lastModifiedTime = inputCdRecord.getLastModificationTime();
394                 if ((lastModifiedDateForNewEntries == -1)
395                         || (lastModifiedDate > lastModifiedDateForNewEntries)
396                         || ((lastModifiedDate == lastModifiedDateForNewEntries)
397                                 && (lastModifiedTime > lastModifiedTimeForNewEntries))) {
398                     lastModifiedDateForNewEntries = lastModifiedDate;
399                     lastModifiedTimeForNewEntries = lastModifiedTime;
400                 }
401 
402                 inspectEntryRequest = signerEngine.outputJarEntry(entryName);
403                 if (inspectEntryRequest != null) {
404                     fulfillInspectInputJarEntryRequest(
405                             inputApkLfhSection, inputLocalFileRecord, inspectEntryRequest);
406                 }
407 
408                 // Output entry's Local File Header + data
409                 long outputLocalFileHeaderOffset = outputOffset;
410                 OutputSizeAndDataOffset outputLfrResult =
411                         outputInputJarEntryLfhRecordPreservingDataAlignment(
412                                 inputApkLfhSection,
413                                 inputLocalFileRecord,
414                                 outputApkOut,
415                                 outputLocalFileHeaderOffset);
416                 outputOffset += outputLfrResult.outputBytes;
417                 long outputDataOffset =
418                         outputLocalFileHeaderOffset + outputLfrResult.dataOffsetBytes;
419 
420                 if (pinPatterns != null) {
421                     boolean pinFileHeader = false;
422                     for (Hints.PatternWithRange pinPattern : pinPatterns) {
423                         if (pinPattern.matcher(inputCdRecord.getName()).matches()) {
424                             Hints.ByteRange dataRange =
425                                     new Hints.ByteRange(outputDataOffset, outputOffset);
426                             Hints.ByteRange pinRange =
427                                     pinPattern.ClampToAbsoluteByteRange(dataRange);
428                             if (pinRange != null) {
429                                 pinFileHeader = true;
430                                 pinByteRanges.add(pinRange);
431                             }
432                         }
433                     }
434                     if (pinFileHeader) {
435                         pinByteRanges.add(
436                                 new Hints.ByteRange(outputLocalFileHeaderOffset, outputDataOffset));
437                     }
438                 }
439 
440                 // Enqueue entry's Central Directory record for output
441                 CentralDirectoryRecord outputCdRecord;
442                 if (outputLocalFileHeaderOffset == inputLocalFileRecord.getStartOffsetInArchive()) {
443                     outputCdRecord = inputCdRecord;
444                 } else {
445                     outputCdRecord =
446                             inputCdRecord.createWithModifiedLocalFileHeaderOffset(
447                                     outputLocalFileHeaderOffset);
448                 }
449                 outputCdRecordsByName.put(entryName, outputCdRecord);
450             }
451         }
452         long inputLfhSectionSize = inputApkLfhSection.size();
453         if (inputOffset < inputLfhSectionSize) {
454             // Unprocessed data in input starting at inputOffset and ending and the end of the input
455             // APK's LFH section. We output this data verbatim because this signer is supposed
456             // to preserve as much of input as possible.
457             long chunkSize = inputLfhSectionSize - inputOffset;
458             inputApkLfhSection.feed(inputOffset, chunkSize, outputApkOut);
459             outputOffset += chunkSize;
460             inputOffset = inputLfhSectionSize;
461         }
462 
463         // Step 6. Sort output APK's Central Directory records in the order in which they should
464         // appear in the output
465         List<CentralDirectoryRecord> outputCdRecords = new ArrayList<>(inputCdRecords.size() + 10);
466         for (CentralDirectoryRecord inputCdRecord : inputCdRecords) {
467             String entryName = inputCdRecord.getName();
468             CentralDirectoryRecord outputCdRecord = outputCdRecordsByName.get(entryName);
469             if (outputCdRecord != null) {
470                 outputCdRecords.add(outputCdRecord);
471             }
472         }
473 
474         if (lastModifiedDateForNewEntries == -1) {
475             lastModifiedDateForNewEntries = 0x3a21; // Jan 1 2009 (DOS)
476             lastModifiedTimeForNewEntries = 0;
477         }
478 
479         // Step 7. Generate and output SourceStamp certificate hash, if necessary. This may output
480         // more Local File Header + data entries and add to the list of output Central Directory
481         // records.
482         if (signerEngine.isEligibleForSourceStamp()) {
483             byte[] uncompressedData = signerEngine.generateSourceStampCertificateDigest();
484             if (mForceSourceStampOverwrite
485                     || sourceStampCertificateDigest == null
486                     || Arrays.equals(uncompressedData, sourceStampCertificateDigest)) {
487                 outputOffset +=
488                         outputDataToOutputApk(
489                                 SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME,
490                                 uncompressedData,
491                                 outputOffset,
492                                 outputCdRecords,
493                                 lastModifiedTimeForNewEntries,
494                                 lastModifiedDateForNewEntries,
495                                 outputApkOut);
496             } else {
497                 throw new ApkFormatException(
498                         String.format(
499                                 "Cannot generate SourceStamp. APK contains an existing entry with"
500                                     + " the name: %s, and it is different than the provided source"
501                                     + " stamp certificate",
502                                 SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME));
503             }
504         }
505 
506         // Step 8. Generate and output JAR signatures, if necessary. This may output more Local File
507         // Header + data entries and add to the list of output Central Directory records.
508         ApkSignerEngine.OutputJarSignatureRequest outputJarSignatureRequest =
509                 signerEngine.outputJarEntries();
510         if (outputJarSignatureRequest != null) {
511             for (ApkSignerEngine.OutputJarSignatureRequest.JarEntry entry :
512                     outputJarSignatureRequest.getAdditionalJarEntries()) {
513                 String entryName = entry.getName();
514                 byte[] uncompressedData = entry.getData();
515 
516                 ApkSignerEngine.InspectJarEntryRequest inspectEntryRequest =
517                         signerEngine.outputJarEntry(entryName);
518                 if (inspectEntryRequest != null) {
519                     inspectEntryRequest
520                             .getDataSink()
521                             .consume(uncompressedData, 0, uncompressedData.length);
522                     inspectEntryRequest.done();
523                 }
524 
525                 outputOffset +=
526                         outputDataToOutputApk(
527                                 entryName,
528                                 uncompressedData,
529                                 outputOffset,
530                                 outputCdRecords,
531                                 lastModifiedTimeForNewEntries,
532                                 lastModifiedDateForNewEntries,
533                                 outputApkOut);
534             }
535             outputJarSignatureRequest.done();
536         }
537 
538         if (pinByteRanges != null) {
539             pinByteRanges.add(new Hints.ByteRange(outputOffset, Long.MAX_VALUE)); // central dir
540             String entryName = Hints.PIN_BYTE_RANGE_ZIP_ENTRY_NAME;
541             byte[] uncompressedData = Hints.encodeByteRangeList(pinByteRanges);
542             outputOffset +=
543                     outputDataToOutputApk(
544                             entryName,
545                             uncompressedData,
546                             outputOffset,
547                             outputCdRecords,
548                             lastModifiedTimeForNewEntries,
549                             lastModifiedDateForNewEntries,
550                             outputApkOut);
551         }
552 
553         // Step 9. Construct output ZIP Central Directory in an in-memory buffer
554         long outputCentralDirSizeBytes = 0;
555         for (CentralDirectoryRecord record : outputCdRecords) {
556             outputCentralDirSizeBytes += record.getSize();
557         }
558         if (outputCentralDirSizeBytes > Integer.MAX_VALUE) {
559             throw new IOException(
560                     "Output ZIP Central Directory too large: "
561                             + outputCentralDirSizeBytes
562                             + " bytes");
563         }
564         ByteBuffer outputCentralDir = ByteBuffer.allocate((int) outputCentralDirSizeBytes);
565         for (CentralDirectoryRecord record : outputCdRecords) {
566             record.copyTo(outputCentralDir);
567         }
568         outputCentralDir.flip();
569         DataSource outputCentralDirDataSource = new ByteBufferDataSource(outputCentralDir);
570         long outputCentralDirStartOffset = outputOffset;
571         int outputCentralDirRecordCount = outputCdRecords.size();
572 
573         // Step 10. Construct output ZIP End of Central Directory record in an in-memory buffer
574         ByteBuffer outputEocd =
575                 EocdRecord.createWithModifiedCentralDirectoryInfo(
576                         inputZipSections.getZipEndOfCentralDirectory(),
577                         outputCentralDirRecordCount,
578                         outputCentralDirDataSource.size(),
579                         outputCentralDirStartOffset);
580 
581         // Step 11. Generate and output APK Signature Scheme v2 and/or v3 signatures and/or
582         // SourceStamp signatures, if necessary.
583         // This may insert an APK Signing Block just before the output's ZIP Central Directory
584         ApkSignerEngine.OutputApkSigningBlockRequest2 outputApkSigningBlockRequest =
585                 signerEngine.outputZipSections2(
586                         outputApkIn,
587                         outputCentralDirDataSource,
588                         DataSources.asDataSource(outputEocd));
589 
590         if (outputApkSigningBlockRequest != null) {
591             int padding = outputApkSigningBlockRequest.getPaddingSizeBeforeApkSigningBlock();
592             outputApkOut.consume(ByteBuffer.allocate(padding));
593             byte[] outputApkSigningBlock = outputApkSigningBlockRequest.getApkSigningBlock();
594             outputApkOut.consume(outputApkSigningBlock, 0, outputApkSigningBlock.length);
595             ZipUtils.setZipEocdCentralDirectoryOffset(
596                     outputEocd,
597                     outputCentralDirStartOffset + padding + outputApkSigningBlock.length);
598             outputApkSigningBlockRequest.done();
599         }
600 
601         // Step 12. Output ZIP Central Directory and ZIP End of Central Directory
602         outputCentralDirDataSource.feed(0, outputCentralDirDataSource.size(), outputApkOut);
603         outputApkOut.consume(outputEocd);
604         signerEngine.outputDone();
605 
606         // Step 13. Generate and output APK Signature Scheme v4 signatures, if necessary.
607         if (mV4SigningEnabled) {
608             signerEngine.signV4(outputApkIn, mOutputV4File, !mV4ErrorReportingEnabled);
609         }
610     }
611 
outputDataToOutputApk( String entryName, byte[] uncompressedData, long localFileHeaderOffset, List<CentralDirectoryRecord> outputCdRecords, int lastModifiedTimeForNewEntries, int lastModifiedDateForNewEntries, DataSink outputApkOut)612     private static long outputDataToOutputApk(
613             String entryName,
614             byte[] uncompressedData,
615             long localFileHeaderOffset,
616             List<CentralDirectoryRecord> outputCdRecords,
617             int lastModifiedTimeForNewEntries,
618             int lastModifiedDateForNewEntries,
619             DataSink outputApkOut)
620             throws IOException {
621         ZipUtils.DeflateResult deflateResult = ZipUtils.deflate(ByteBuffer.wrap(uncompressedData));
622         byte[] compressedData = deflateResult.output;
623         long uncompressedDataCrc32 = deflateResult.inputCrc32;
624         long numOfDataBytes =
625                 LocalFileRecord.outputRecordWithDeflateCompressedData(
626                         entryName,
627                         lastModifiedTimeForNewEntries,
628                         lastModifiedDateForNewEntries,
629                         compressedData,
630                         uncompressedDataCrc32,
631                         uncompressedData.length,
632                         outputApkOut);
633         outputCdRecords.add(
634                 CentralDirectoryRecord.createWithDeflateCompressedData(
635                         entryName,
636                         lastModifiedTimeForNewEntries,
637                         lastModifiedDateForNewEntries,
638                         uncompressedDataCrc32,
639                         compressedData.length,
640                         uncompressedData.length,
641                         localFileHeaderOffset));
642         return numOfDataBytes;
643     }
644 
fulfillInspectInputJarEntryRequest( DataSource lfhSection, LocalFileRecord localFileRecord, ApkSignerEngine.InspectJarEntryRequest inspectEntryRequest)645     private static void fulfillInspectInputJarEntryRequest(
646             DataSource lfhSection,
647             LocalFileRecord localFileRecord,
648             ApkSignerEngine.InspectJarEntryRequest inspectEntryRequest)
649             throws IOException, ApkFormatException {
650         try {
651             localFileRecord.outputUncompressedData(lfhSection, inspectEntryRequest.getDataSink());
652         } catch (ZipFormatException e) {
653             throw new ApkFormatException("Malformed ZIP entry: " + localFileRecord.getName(), e);
654         }
655         inspectEntryRequest.done();
656     }
657 
658     private static class OutputSizeAndDataOffset {
659         public long outputBytes;
660         public long dataOffsetBytes;
661 
OutputSizeAndDataOffset(long outputBytes, long dataOffsetBytes)662         public OutputSizeAndDataOffset(long outputBytes, long dataOffsetBytes) {
663             this.outputBytes = outputBytes;
664             this.dataOffsetBytes = dataOffsetBytes;
665         }
666     }
667 
outputInputJarEntryLfhRecordPreservingDataAlignment( DataSource inputLfhSection, LocalFileRecord inputRecord, DataSink outputLfhSection, long outputOffset)668     private static OutputSizeAndDataOffset outputInputJarEntryLfhRecordPreservingDataAlignment(
669             DataSource inputLfhSection,
670             LocalFileRecord inputRecord,
671             DataSink outputLfhSection,
672             long outputOffset)
673             throws IOException {
674         long inputOffset = inputRecord.getStartOffsetInArchive();
675         if (inputOffset == outputOffset) {
676             // This record's data will be aligned same as in the input APK.
677             return new OutputSizeAndDataOffset(
678                     inputRecord.outputRecord(inputLfhSection, outputLfhSection),
679                     inputRecord.getDataStartOffsetInRecord());
680         }
681         int dataAlignmentMultiple = getInputJarEntryDataAlignmentMultiple(inputRecord);
682         if ((dataAlignmentMultiple <= 1)
683                 || ((inputOffset % dataAlignmentMultiple)
684                         == (outputOffset % dataAlignmentMultiple))) {
685             // This record's data will be aligned same as in the input APK.
686             return new OutputSizeAndDataOffset(
687                     inputRecord.outputRecord(inputLfhSection, outputLfhSection),
688                     inputRecord.getDataStartOffsetInRecord());
689         }
690 
691         long inputDataStartOffset = inputOffset + inputRecord.getDataStartOffsetInRecord();
692         if ((inputDataStartOffset % dataAlignmentMultiple) != 0) {
693             // This record's data is not aligned in the input APK. No need to align it in the
694             // output.
695             return new OutputSizeAndDataOffset(
696                     inputRecord.outputRecord(inputLfhSection, outputLfhSection),
697                     inputRecord.getDataStartOffsetInRecord());
698         }
699 
700         // This record's data needs to be re-aligned in the output. This is achieved using the
701         // record's extra field.
702         ByteBuffer aligningExtra =
703                 createExtraFieldToAlignData(
704                         inputRecord.getExtra(),
705                         outputOffset + inputRecord.getExtraFieldStartOffsetInsideRecord(),
706                         dataAlignmentMultiple);
707         long dataOffset =
708                 (long) inputRecord.getDataStartOffsetInRecord()
709                         + aligningExtra.remaining()
710                         - inputRecord.getExtra().remaining();
711         return new OutputSizeAndDataOffset(
712                 inputRecord.outputRecordWithModifiedExtra(
713                         inputLfhSection, aligningExtra, outputLfhSection),
714                 dataOffset);
715     }
716 
getInputJarEntryDataAlignmentMultiple(LocalFileRecord entry)717     private static int getInputJarEntryDataAlignmentMultiple(LocalFileRecord entry) {
718         if (entry.isDataCompressed()) {
719             // Compressed entries don't need to be aligned
720             return 1;
721         }
722 
723         // Attempt to obtain the alignment multiple from the entry's extra field.
724         ByteBuffer extra = entry.getExtra();
725         if (extra.hasRemaining()) {
726             extra.order(ByteOrder.LITTLE_ENDIAN);
727             // FORMAT: sequence of fields. Each field consists of:
728             //   * uint16 ID
729             //   * uint16 size
730             //   * 'size' bytes: payload
731             while (extra.remaining() >= 4) {
732                 short headerId = extra.getShort();
733                 int dataSize = ZipUtils.getUnsignedInt16(extra);
734                 if (dataSize > extra.remaining()) {
735                     // Malformed field -- insufficient input remaining
736                     break;
737                 }
738                 if (headerId != ALIGNMENT_ZIP_EXTRA_DATA_FIELD_HEADER_ID) {
739                     // Skip this field
740                     extra.position(extra.position() + dataSize);
741                     continue;
742                 }
743                 // This is APK alignment field.
744                 // FORMAT:
745                 //  * uint16 alignment multiple (in bytes)
746                 //  * remaining bytes -- padding to achieve alignment of data which starts after
747                 //    the extra field
748                 if (dataSize < 2) {
749                     // Malformed
750                     break;
751                 }
752                 return ZipUtils.getUnsignedInt16(extra);
753             }
754         }
755 
756         // Fall back to filename-based defaults
757         return (entry.getName().endsWith(".so")) ? ANDROID_COMMON_PAGE_ALIGNMENT_BYTES : 4;
758     }
759 
createExtraFieldToAlignData( ByteBuffer original, long extraStartOffset, int dataAlignmentMultiple)760     private static ByteBuffer createExtraFieldToAlignData(
761             ByteBuffer original, long extraStartOffset, int dataAlignmentMultiple) {
762         if (dataAlignmentMultiple <= 1) {
763             return original;
764         }
765 
766         // In the worst case scenario, we'll increase the output size by 6 + dataAlignment - 1.
767         ByteBuffer result = ByteBuffer.allocate(original.remaining() + 5 + dataAlignmentMultiple);
768         result.order(ByteOrder.LITTLE_ENDIAN);
769 
770         // Step 1. Output all extra fields other than the one which is to do with alignment
771         // FORMAT: sequence of fields. Each field consists of:
772         //   * uint16 ID
773         //   * uint16 size
774         //   * 'size' bytes: payload
775         while (original.remaining() >= 4) {
776             short headerId = original.getShort();
777             int dataSize = ZipUtils.getUnsignedInt16(original);
778             if (dataSize > original.remaining()) {
779                 // Malformed field -- insufficient input remaining
780                 break;
781             }
782             if (((headerId == 0) && (dataSize == 0))
783                     || (headerId == ALIGNMENT_ZIP_EXTRA_DATA_FIELD_HEADER_ID)) {
784                 // Ignore the field if it has to do with the old APK data alignment method (filling
785                 // the extra field with 0x00 bytes) or the new APK data alignment method.
786                 original.position(original.position() + dataSize);
787                 continue;
788             }
789             // Copy this field (including header) to the output
790             original.position(original.position() - 4);
791             int originalLimit = original.limit();
792             original.limit(original.position() + 4 + dataSize);
793             result.put(original);
794             original.limit(originalLimit);
795         }
796 
797         // Step 2. Add alignment field
798         // FORMAT:
799         //  * uint16 extra header ID
800         //  * uint16 extra data size
801         //        Payload ('data size' bytes)
802         //      * uint16 alignment multiple (in bytes)
803         //      * remaining bytes -- padding to achieve alignment of data which starts after the
804         //        extra field
805         long dataMinStartOffset =
806                 extraStartOffset
807                         + result.position()
808                         + ALIGNMENT_ZIP_EXTRA_DATA_FIELD_MIN_SIZE_BYTES;
809         int paddingSizeBytes =
810                 (dataAlignmentMultiple - ((int) (dataMinStartOffset % dataAlignmentMultiple)))
811                         % dataAlignmentMultiple;
812         result.putShort(ALIGNMENT_ZIP_EXTRA_DATA_FIELD_HEADER_ID);
813         ZipUtils.putUnsignedInt16(result, 2 + paddingSizeBytes);
814         ZipUtils.putUnsignedInt16(result, dataAlignmentMultiple);
815         result.position(result.position() + paddingSizeBytes);
816         result.flip();
817 
818         return result;
819     }
820 
getZipCentralDirectory( DataSource apk, ApkUtils.ZipSections apkSections)821     private static ByteBuffer getZipCentralDirectory(
822             DataSource apk, ApkUtils.ZipSections apkSections)
823             throws IOException, ApkFormatException {
824         long cdSizeBytes = apkSections.getZipCentralDirectorySizeBytes();
825         if (cdSizeBytes > Integer.MAX_VALUE) {
826             throw new ApkFormatException("ZIP Central Directory too large: " + cdSizeBytes);
827         }
828         long cdOffset = apkSections.getZipCentralDirectoryOffset();
829         ByteBuffer cd = apk.getByteBuffer(cdOffset, (int) cdSizeBytes);
830         cd.order(ByteOrder.LITTLE_ENDIAN);
831         return cd;
832     }
833 
parseZipCentralDirectory( ByteBuffer cd, ApkUtils.ZipSections apkSections)834     private static List<CentralDirectoryRecord> parseZipCentralDirectory(
835             ByteBuffer cd, ApkUtils.ZipSections apkSections) throws ApkFormatException {
836         long cdOffset = apkSections.getZipCentralDirectoryOffset();
837         int expectedCdRecordCount = apkSections.getZipCentralDirectoryRecordCount();
838         List<CentralDirectoryRecord> cdRecords = new ArrayList<>(expectedCdRecordCount);
839         Set<String> entryNames = new HashSet<>(expectedCdRecordCount);
840         for (int i = 0; i < expectedCdRecordCount; i++) {
841             CentralDirectoryRecord cdRecord;
842             int offsetInsideCd = cd.position();
843             try {
844                 cdRecord = CentralDirectoryRecord.getRecord(cd);
845             } catch (ZipFormatException e) {
846                 throw new ApkFormatException(
847                         "Malformed ZIP Central Directory record #"
848                                 + (i + 1)
849                                 + " at file offset "
850                                 + (cdOffset + offsetInsideCd),
851                         e);
852             }
853             String entryName = cdRecord.getName();
854             if (!entryNames.add(entryName)) {
855                 throw new ApkFormatException(
856                         "Multiple ZIP entries with the same name: " + entryName);
857             }
858             cdRecords.add(cdRecord);
859         }
860         if (cd.hasRemaining()) {
861             throw new ApkFormatException(
862                     "Unused space at the end of ZIP Central Directory: "
863                             + cd.remaining()
864                             + " bytes starting at file offset "
865                             + (cdOffset + cd.position()));
866         }
867 
868         return cdRecords;
869     }
870 
findCdRecord( List<CentralDirectoryRecord> cdRecords, String name)871     private static CentralDirectoryRecord findCdRecord(
872             List<CentralDirectoryRecord> cdRecords, String name) {
873         for (CentralDirectoryRecord cdRecord : cdRecords) {
874             if (name.equals(cdRecord.getName())) {
875                 return cdRecord;
876             }
877         }
878         return null;
879     }
880 
881     /**
882      * Returns the contents of the APK's {@code AndroidManifest.xml} or {@code null} if this entry
883      * is not present in the APK.
884      */
getAndroidManifestFromApk( List<CentralDirectoryRecord> cdRecords, DataSource lhfSection)885     static ByteBuffer getAndroidManifestFromApk(
886             List<CentralDirectoryRecord> cdRecords, DataSource lhfSection)
887             throws IOException, ApkFormatException, ZipFormatException {
888         CentralDirectoryRecord androidManifestCdRecord =
889                 findCdRecord(cdRecords, ANDROID_MANIFEST_ZIP_ENTRY_NAME);
890         if (androidManifestCdRecord == null) {
891             throw new ApkFormatException("Missing " + ANDROID_MANIFEST_ZIP_ENTRY_NAME);
892         }
893 
894         return ByteBuffer.wrap(
895                 LocalFileRecord.getUncompressedData(
896                         lhfSection, androidManifestCdRecord, lhfSection.size()));
897     }
898 
899     /**
900      * Return list of pin patterns embedded in the pin pattern asset file. If no such file, return
901      * {@code null}.
902      */
extractPinPatterns( List<CentralDirectoryRecord> cdRecords, DataSource lhfSection)903     private static List<Hints.PatternWithRange> extractPinPatterns(
904             List<CentralDirectoryRecord> cdRecords, DataSource lhfSection)
905             throws IOException, ApkFormatException {
906         CentralDirectoryRecord pinListCdRecord =
907                 findCdRecord(cdRecords, Hints.PIN_HINT_ASSET_ZIP_ENTRY_NAME);
908         List<Hints.PatternWithRange> pinPatterns = null;
909         if (pinListCdRecord != null) {
910             pinPatterns = new ArrayList<>();
911             byte[] patternBlob;
912             try {
913                 patternBlob =
914                         LocalFileRecord.getUncompressedData(
915                                 lhfSection, pinListCdRecord, lhfSection.size());
916             } catch (ZipFormatException ex) {
917                 throw new ApkFormatException("Bad " + pinListCdRecord);
918             }
919             pinPatterns = Hints.parsePinPatterns(patternBlob);
920         }
921         return pinPatterns;
922     }
923 
924     /**
925      * Returns the minimum Android version (API Level) supported by the provided APK. This is based
926      * on the {@code android:minSdkVersion} attributes of the APK's {@code AndroidManifest.xml}.
927      */
getMinSdkVersionFromApk( List<CentralDirectoryRecord> cdRecords, DataSource lhfSection)928     private static int getMinSdkVersionFromApk(
929             List<CentralDirectoryRecord> cdRecords, DataSource lhfSection)
930             throws IOException, MinSdkVersionException {
931         ByteBuffer androidManifest;
932         try {
933             androidManifest = getAndroidManifestFromApk(cdRecords, lhfSection);
934         } catch (ZipFormatException | ApkFormatException e) {
935             throw new MinSdkVersionException(
936                     "Failed to determine APK's minimum supported Android platform version", e);
937         }
938         return ApkUtils.getMinSdkVersionFromBinaryAndroidManifest(androidManifest);
939     }
940 
941     /**
942      * Configuration of a signer.
943      *
944      * <p>Use {@link Builder} to obtain configuration instances.
945      */
946     public static class SignerConfig {
947         private final String mName;
948         private final PrivateKey mPrivateKey;
949         private final List<X509Certificate> mCertificates;
950 
SignerConfig( String name, PrivateKey privateKey, List<X509Certificate> certificates)951         private SignerConfig(
952                 String name, PrivateKey privateKey, List<X509Certificate> certificates) {
953             mName = name;
954             mPrivateKey = privateKey;
955             mCertificates = Collections.unmodifiableList(new ArrayList<>(certificates));
956         }
957 
958         /** Returns the name of this signer. */
getName()959         public String getName() {
960             return mName;
961         }
962 
963         /** Returns the signing key of this signer. */
getPrivateKey()964         public PrivateKey getPrivateKey() {
965             return mPrivateKey;
966         }
967 
968         /**
969          * Returns the certificate(s) of this signer. The first certificate's public key corresponds
970          * to this signer's private key.
971          */
getCertificates()972         public List<X509Certificate> getCertificates() {
973             return mCertificates;
974         }
975 
976         /** Builder of {@link SignerConfig} instances. */
977         public static class Builder {
978             private final String mName;
979             private final PrivateKey mPrivateKey;
980             private final List<X509Certificate> mCertificates;
981 
982             /**
983              * Constructs a new {@code Builder}.
984              *
985              * @param name signer's name. The name is reflected in the name of files comprising the
986              *     JAR signature of the APK.
987              * @param privateKey signing key
988              * @param certificates list of one or more X.509 certificates. The subject public key of
989              *     the first certificate must correspond to the {@code privateKey}.
990              */
Builder(String name, PrivateKey privateKey, List<X509Certificate> certificates)991             public Builder(String name, PrivateKey privateKey, List<X509Certificate> certificates) {
992                 if (name.isEmpty()) {
993                     throw new IllegalArgumentException("Empty name");
994                 }
995                 mName = name;
996                 mPrivateKey = privateKey;
997                 mCertificates = new ArrayList<>(certificates);
998             }
999 
1000             /**
1001              * Returns a new {@code SignerConfig} instance configured based on the configuration of
1002              * this builder.
1003              */
build()1004             public SignerConfig build() {
1005                 return new SignerConfig(mName, mPrivateKey, mCertificates);
1006             }
1007         }
1008     }
1009 
1010     /**
1011      * Builder of {@link ApkSigner} instances.
1012      *
1013      * <p>The builder requires the following information to construct a working {@code ApkSigner}:
1014      *
1015      * <ul>
1016      *   <li>Signer configs or {@link ApkSignerEngine} -- provided in the constructor,
1017      *   <li>APK to be signed -- see {@link #setInputApk(File) setInputApk} variants,
1018      *   <li>where to store the output signed APK -- see {@link #setOutputApk(File) setOutputApk}
1019      *       variants.
1020      * </ul>
1021      */
1022     public static class Builder {
1023         private final List<SignerConfig> mSignerConfigs;
1024         private SignerConfig mSourceStampSignerConfig;
1025         private boolean mForceSourceStampOverwrite = false;
1026         private boolean mV1SigningEnabled = true;
1027         private boolean mV2SigningEnabled = true;
1028         private boolean mV3SigningEnabled = true;
1029         private boolean mV4SigningEnabled = true;
1030         private boolean mVerityEnabled = false;
1031         private boolean mV4ErrorReportingEnabled = false;
1032         private boolean mDebuggableApkPermitted = true;
1033         private boolean mOtherSignersSignaturesPreserved;
1034         private String mCreatedBy;
1035         private Integer mMinSdkVersion;
1036 
1037         private final ApkSignerEngine mSignerEngine;
1038 
1039         private File mInputApkFile;
1040         private DataSource mInputApkDataSource;
1041 
1042         private File mOutputApkFile;
1043         private DataSink mOutputApkDataSink;
1044         private DataSource mOutputApkDataSource;
1045 
1046         private File mOutputV4File;
1047 
1048         private SigningCertificateLineage mSigningCertificateLineage;
1049 
1050         // APK Signature Scheme v3 only supports a single signing certificate, so to move to v3
1051         // signing by default, but not require prior clients to update to explicitly disable v3
1052         // signing for multiple signers, we modify the mV3SigningEnabled depending on the provided
1053         // inputs (multiple signers and mSigningCertificateLineage in particular).  Maintain two
1054         // extra variables to record whether or not mV3SigningEnabled has been set directly by a
1055         // client and so should override the default behavior.
1056         private boolean mV3SigningExplicitlyDisabled = false;
1057         private boolean mV3SigningExplicitlyEnabled = false;
1058 
1059         /**
1060          * Constructs a new {@code Builder} for an {@code ApkSigner} which signs using the provided
1061          * signer configurations. The resulting signer may be further customized through this
1062          * builder's setters, such as {@link #setMinSdkVersion(int)}, {@link
1063          * #setV1SigningEnabled(boolean)}, {@link #setV2SigningEnabled(boolean)}, {@link
1064          * #setOtherSignersSignaturesPreserved(boolean)}, {@link #setCreatedBy(String)}.
1065          *
1066          * <p>{@link #Builder(ApkSignerEngine)} is an alternative for advanced use cases where more
1067          * control over low-level details of signing is desired.
1068          */
Builder(List<SignerConfig> signerConfigs)1069         public Builder(List<SignerConfig> signerConfigs) {
1070             if (signerConfigs.isEmpty()) {
1071                 throw new IllegalArgumentException("At least one signer config must be provided");
1072             }
1073             if (signerConfigs.size() > 1) {
1074                 // APK Signature Scheme v3 only supports single signer, unless a
1075                 // SigningCertificateLineage is provided, in which case this will be reset to true,
1076                 // since we don't yet have a v4 scheme about which to worry
1077                 mV3SigningEnabled = false;
1078             }
1079             mSignerConfigs = new ArrayList<>(signerConfigs);
1080             mSignerEngine = null;
1081         }
1082 
1083         /**
1084          * Constructs a new {@code Builder} for an {@code ApkSigner} which signs using the provided
1085          * signing engine. This is meant for advanced use cases where more control is needed over
1086          * the lower-level details of signing. For typical use cases, {@link #Builder(List)} is more
1087          * appropriate.
1088          */
Builder(ApkSignerEngine signerEngine)1089         public Builder(ApkSignerEngine signerEngine) {
1090             if (signerEngine == null) {
1091                 throw new NullPointerException("signerEngine == null");
1092             }
1093             mSignerEngine = signerEngine;
1094             mSignerConfigs = null;
1095         }
1096 
1097         /** Sets the signing configuration of the source stamp to be embedded in the APK. */
setSourceStampSignerConfig(SignerConfig sourceStampSignerConfig)1098         public Builder setSourceStampSignerConfig(SignerConfig sourceStampSignerConfig) {
1099             mSourceStampSignerConfig = sourceStampSignerConfig;
1100             return this;
1101         }
1102 
1103         /**
1104          * Sets whether the APK should overwrite existing source stamp, if found.
1105          *
1106          * @param force {@code true} to require the APK to be overwrite existing source stamp
1107          */
setForceSourceStampOverwrite(boolean force)1108         public Builder setForceSourceStampOverwrite(boolean force) {
1109             mForceSourceStampOverwrite = force;
1110             return this;
1111         }
1112 
1113         /**
1114          * Sets the APK to be signed.
1115          *
1116          * @see #setInputApk(DataSource)
1117          */
setInputApk(File inputApk)1118         public Builder setInputApk(File inputApk) {
1119             if (inputApk == null) {
1120                 throw new NullPointerException("inputApk == null");
1121             }
1122             mInputApkFile = inputApk;
1123             mInputApkDataSource = null;
1124             return this;
1125         }
1126 
1127         /**
1128          * Sets the APK to be signed.
1129          *
1130          * @see #setInputApk(File)
1131          */
setInputApk(DataSource inputApk)1132         public Builder setInputApk(DataSource inputApk) {
1133             if (inputApk == null) {
1134                 throw new NullPointerException("inputApk == null");
1135             }
1136             mInputApkDataSource = inputApk;
1137             mInputApkFile = null;
1138             return this;
1139         }
1140 
1141         /**
1142          * Sets the location of the output (signed) APK. {@code ApkSigner} will create this file if
1143          * it doesn't exist.
1144          *
1145          * @see #setOutputApk(ReadableDataSink)
1146          * @see #setOutputApk(DataSink, DataSource)
1147          */
setOutputApk(File outputApk)1148         public Builder setOutputApk(File outputApk) {
1149             if (outputApk == null) {
1150                 throw new NullPointerException("outputApk == null");
1151             }
1152             mOutputApkFile = outputApk;
1153             mOutputApkDataSink = null;
1154             mOutputApkDataSource = null;
1155             return this;
1156         }
1157 
1158         /**
1159          * Sets the readable data sink which will receive the output (signed) APK. After signing,
1160          * the contents of the output APK will be available via the {@link DataSource} interface of
1161          * the sink.
1162          *
1163          * <p>This variant of {@code setOutputApk} is useful for avoiding writing the output APK to
1164          * a file. For example, an in-memory data sink, such as {@link
1165          * DataSinks#newInMemoryDataSink()}, could be used instead of a file.
1166          *
1167          * @see #setOutputApk(File)
1168          * @see #setOutputApk(DataSink, DataSource)
1169          */
setOutputApk(ReadableDataSink outputApk)1170         public Builder setOutputApk(ReadableDataSink outputApk) {
1171             if (outputApk == null) {
1172                 throw new NullPointerException("outputApk == null");
1173             }
1174             return setOutputApk(outputApk, outputApk);
1175         }
1176 
1177         /**
1178          * Sets the sink which will receive the output (signed) APK. Data received by the {@code
1179          * outputApkOut} sink must be visible through the {@code outputApkIn} data source.
1180          *
1181          * <p>This is an advanced variant of {@link #setOutputApk(ReadableDataSink)}, enabling the
1182          * sink and the source to be different objects.
1183          *
1184          * @see #setOutputApk(ReadableDataSink)
1185          * @see #setOutputApk(File)
1186          */
setOutputApk(DataSink outputApkOut, DataSource outputApkIn)1187         public Builder setOutputApk(DataSink outputApkOut, DataSource outputApkIn) {
1188             if (outputApkOut == null) {
1189                 throw new NullPointerException("outputApkOut == null");
1190             }
1191             if (outputApkIn == null) {
1192                 throw new NullPointerException("outputApkIn == null");
1193             }
1194             mOutputApkFile = null;
1195             mOutputApkDataSink = outputApkOut;
1196             mOutputApkDataSource = outputApkIn;
1197             return this;
1198         }
1199 
1200         /**
1201          * Sets the location of the V4 output file. {@code ApkSigner} will create this file if it
1202          * doesn't exist.
1203          */
setV4SignatureOutputFile(File v4SignatureOutputFile)1204         public Builder setV4SignatureOutputFile(File v4SignatureOutputFile) {
1205             if (v4SignatureOutputFile == null) {
1206                 throw new NullPointerException("v4HashRootOutputFile == null");
1207             }
1208             mOutputV4File = v4SignatureOutputFile;
1209             return this;
1210         }
1211 
1212         /**
1213          * Sets the minimum Android platform version (API Level) on which APK signatures produced by
1214          * the signer being built must verify. This method is useful for overriding the default
1215          * behavior where the minimum API Level is obtained from the {@code android:minSdkVersion}
1216          * attribute of the APK's {@code AndroidManifest.xml}.
1217          *
1218          * <p><em>Note:</em> This method may result in APK signatures which don't verify on some
1219          * Android platform versions supported by the APK.
1220          *
1221          * <p><em>Note:</em> This method may only be invoked when this builder is not initialized
1222          * with an {@link ApkSignerEngine}.
1223          *
1224          * @throws IllegalStateException if this builder was initialized with an {@link
1225          *     ApkSignerEngine}
1226          */
setMinSdkVersion(int minSdkVersion)1227         public Builder setMinSdkVersion(int minSdkVersion) {
1228             checkInitializedWithoutEngine();
1229             mMinSdkVersion = minSdkVersion;
1230             return this;
1231         }
1232 
1233         /**
1234          * Sets whether the APK should be signed using JAR signing (aka v1 signature scheme).
1235          *
1236          * <p>By default, whether APK is signed using JAR signing is determined by {@code
1237          * ApkSigner}, based on the platform versions supported by the APK or specified using {@link
1238          * #setMinSdkVersion(int)}. Disabling JAR signing will result in APK signatures which don't
1239          * verify on Android Marshmallow (Android 6.0, API Level 23) and lower.
1240          *
1241          * <p><em>Note:</em> This method may only be invoked when this builder is not initialized
1242          * with an {@link ApkSignerEngine}.
1243          *
1244          * @param enabled {@code true} to require the APK to be signed using JAR signing, {@code
1245          *     false} to require the APK to not be signed using JAR signing.
1246          * @throws IllegalStateException if this builder was initialized with an {@link
1247          *     ApkSignerEngine}
1248          * @see <a
1249          *     href="https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signed_JAR_File">JAR
1250          *     signing</a>
1251          */
setV1SigningEnabled(boolean enabled)1252         public Builder setV1SigningEnabled(boolean enabled) {
1253             checkInitializedWithoutEngine();
1254             mV1SigningEnabled = enabled;
1255             return this;
1256         }
1257 
1258         /**
1259          * Sets whether the APK should be signed using APK Signature Scheme v2 (aka v2 signature
1260          * scheme).
1261          *
1262          * <p>By default, whether APK is signed using APK Signature Scheme v2 is determined by
1263          * {@code ApkSigner} based on the platform versions supported by the APK or specified using
1264          * {@link #setMinSdkVersion(int)}.
1265          *
1266          * <p><em>Note:</em> This method may only be invoked when this builder is not initialized
1267          * with an {@link ApkSignerEngine}.
1268          *
1269          * @param enabled {@code true} to require the APK to be signed using APK Signature Scheme
1270          *     v2, {@code false} to require the APK to not be signed using APK Signature Scheme v2.
1271          * @throws IllegalStateException if this builder was initialized with an {@link
1272          *     ApkSignerEngine}
1273          * @see <a href="https://source.android.com/security/apksigning/v2.html">APK Signature
1274          *     Scheme v2</a>
1275          */
setV2SigningEnabled(boolean enabled)1276         public Builder setV2SigningEnabled(boolean enabled) {
1277             checkInitializedWithoutEngine();
1278             mV2SigningEnabled = enabled;
1279             return this;
1280         }
1281 
1282         /**
1283          * Sets whether the APK should be signed using APK Signature Scheme v3 (aka v3 signature
1284          * scheme).
1285          *
1286          * <p>By default, whether APK is signed using APK Signature Scheme v3 is determined by
1287          * {@code ApkSigner} based on the platform versions supported by the APK or specified using
1288          * {@link #setMinSdkVersion(int)}.
1289          *
1290          * <p><em>Note:</em> This method may only be invoked when this builder is not initialized
1291          * with an {@link ApkSignerEngine}.
1292          *
1293          * <p><em>Note:</em> APK Signature Scheme v3 only supports a single signing certificate, but
1294          * may take multiple signers mapping to different targeted platform versions.
1295          *
1296          * @param enabled {@code true} to require the APK to be signed using APK Signature Scheme
1297          *     v3, {@code false} to require the APK to not be signed using APK Signature Scheme v3.
1298          * @throws IllegalStateException if this builder was initialized with an {@link
1299          *     ApkSignerEngine}
1300          */
setV3SigningEnabled(boolean enabled)1301         public Builder setV3SigningEnabled(boolean enabled) {
1302             checkInitializedWithoutEngine();
1303             mV3SigningEnabled = enabled;
1304             if (enabled) {
1305                 mV3SigningExplicitlyEnabled = true;
1306             } else {
1307                 mV3SigningExplicitlyDisabled = true;
1308             }
1309             return this;
1310         }
1311 
1312         /**
1313          * Sets whether the APK should be signed using APK Signature Scheme v4.
1314          *
1315          * <p>V4 signing requires that the APK be v2 or v3 signed.
1316          *
1317          * @param enabled {@code true} to require the APK to be signed using APK Signature Scheme v2
1318          *     or v3 and generate an v4 signature file
1319          */
setV4SigningEnabled(boolean enabled)1320         public Builder setV4SigningEnabled(boolean enabled) {
1321             checkInitializedWithoutEngine();
1322             mV4SigningEnabled = enabled;
1323             mV4ErrorReportingEnabled = enabled;
1324             return this;
1325         }
1326 
1327         /**
1328          * Sets whether errors during v4 signing should be reported and halt the signing process.
1329          *
1330          * <p>Error reporting for v4 signing is disabled by default, but will be enabled if the
1331          * caller invokes {@link #setV4SigningEnabled} with a value of true. This method is useful
1332          * for tools that enable v4 signing by default but don't want to fail the signing process if
1333          * the user did not explicitly request the v4 signing.
1334          *
1335          * @param enabled {@code false} to prevent errors encountered during the V4 signing from
1336          *     halting the signing process
1337          */
setV4ErrorReportingEnabled(boolean enabled)1338         public Builder setV4ErrorReportingEnabled(boolean enabled) {
1339             checkInitializedWithoutEngine();
1340             mV4ErrorReportingEnabled = enabled;
1341             return this;
1342         }
1343 
1344         /**
1345          * Sets whether to enable the verity signature algorithm for the v2 and v3 signature
1346          * schemes.
1347          *
1348          * @param enabled {@code true} to enable the verity signature algorithm for inclusion in the
1349          *     v2 and v3 signature blocks.
1350          */
setVerityEnabled(boolean enabled)1351         public Builder setVerityEnabled(boolean enabled) {
1352             checkInitializedWithoutEngine();
1353             mVerityEnabled = enabled;
1354             return this;
1355         }
1356 
1357         /**
1358          * Sets whether the APK should be signed even if it is marked as debuggable ({@code
1359          * android:debuggable="true"} in its {@code AndroidManifest.xml}). For backward
1360          * compatibility reasons, the default value of this setting is {@code true}.
1361          *
1362          * <p>It is dangerous to sign debuggable APKs with production/release keys because Android
1363          * platform loosens security checks for such APKs. For example, arbitrary unauthorized code
1364          * may be executed in the context of such an app by anybody with ADB shell access.
1365          *
1366          * <p><em>Note:</em> This method may only be invoked when this builder is not initialized
1367          * with an {@link ApkSignerEngine}.
1368          */
setDebuggableApkPermitted(boolean permitted)1369         public Builder setDebuggableApkPermitted(boolean permitted) {
1370             checkInitializedWithoutEngine();
1371             mDebuggableApkPermitted = permitted;
1372             return this;
1373         }
1374 
1375         /**
1376          * Sets whether signatures produced by signers other than the ones configured in this engine
1377          * should be copied from the input APK to the output APK.
1378          *
1379          * <p>By default, signatures of other signers are omitted from the output APK.
1380          *
1381          * <p><em>Note:</em> This method may only be invoked when this builder is not initialized
1382          * with an {@link ApkSignerEngine}.
1383          *
1384          * @throws IllegalStateException if this builder was initialized with an {@link
1385          *     ApkSignerEngine}
1386          */
setOtherSignersSignaturesPreserved(boolean preserved)1387         public Builder setOtherSignersSignaturesPreserved(boolean preserved) {
1388             checkInitializedWithoutEngine();
1389             mOtherSignersSignaturesPreserved = preserved;
1390             return this;
1391         }
1392 
1393         /**
1394          * Sets the value of the {@code Created-By} field in JAR signature files.
1395          *
1396          * <p><em>Note:</em> This method may only be invoked when this builder is not initialized
1397          * with an {@link ApkSignerEngine}.
1398          *
1399          * @throws IllegalStateException if this builder was initialized with an {@link
1400          *     ApkSignerEngine}
1401          */
setCreatedBy(String createdBy)1402         public Builder setCreatedBy(String createdBy) {
1403             checkInitializedWithoutEngine();
1404             if (createdBy == null) {
1405                 throw new NullPointerException();
1406             }
1407             mCreatedBy = createdBy;
1408             return this;
1409         }
1410 
checkInitializedWithoutEngine()1411         private void checkInitializedWithoutEngine() {
1412             if (mSignerEngine != null) {
1413                 throw new IllegalStateException(
1414                         "Operation is not available when builder initialized with an engine");
1415             }
1416         }
1417 
1418         /**
1419          * Sets the {@link SigningCertificateLineage} to use with the v3 signature scheme. This
1420          * structure provides proof of signing certificate rotation linking {@link SignerConfig}
1421          * objects to previous ones.
1422          */
setSigningCertificateLineage( SigningCertificateLineage signingCertificateLineage)1423         public Builder setSigningCertificateLineage(
1424                 SigningCertificateLineage signingCertificateLineage) {
1425             if (signingCertificateLineage != null) {
1426                 mV3SigningEnabled = true;
1427                 mSigningCertificateLineage = signingCertificateLineage;
1428             }
1429             return this;
1430         }
1431 
1432         /**
1433          * Returns a new {@code ApkSigner} instance initialized according to the configuration of
1434          * this builder.
1435          */
build()1436         public ApkSigner build() {
1437             if (mV3SigningExplicitlyDisabled && mV3SigningExplicitlyEnabled) {
1438                 throw new IllegalStateException(
1439                         "Builder configured to both enable and disable APK "
1440                                 + "Signature Scheme v3 signing");
1441             }
1442 
1443             if (mV3SigningExplicitlyDisabled) {
1444                 mV3SigningEnabled = false;
1445             }
1446 
1447             if (mV3SigningExplicitlyEnabled) {
1448                 mV3SigningEnabled = true;
1449             }
1450 
1451             // If V4 signing is not explicitly set, and V2/V3 signing is disabled, then V4 signing
1452             // must be disabled as well as it is dependent on V2/V3.
1453             if (mV4SigningEnabled && !mV2SigningEnabled && !mV3SigningEnabled) {
1454                 if (!mV4ErrorReportingEnabled) {
1455                     mV4SigningEnabled = false;
1456                 } else {
1457                     throw new IllegalStateException(
1458                             "APK Signature Scheme v4 signing requires at least "
1459                                     + "v2 or v3 signing to be enabled");
1460                 }
1461             }
1462 
1463             // TODO - if v3 signing is enabled, check provided signers and history to see if valid
1464 
1465             return new ApkSigner(
1466                     mSignerConfigs,
1467                     mSourceStampSignerConfig,
1468                     mForceSourceStampOverwrite,
1469                     mMinSdkVersion,
1470                     mV1SigningEnabled,
1471                     mV2SigningEnabled,
1472                     mV3SigningEnabled,
1473                     mV4SigningEnabled,
1474                     mVerityEnabled,
1475                     mV4ErrorReportingEnabled,
1476                     mDebuggableApkPermitted,
1477                     mOtherSignersSignaturesPreserved,
1478                     mCreatedBy,
1479                     mSignerEngine,
1480                     mInputApkFile,
1481                     mInputApkDataSource,
1482                     mOutputApkFile,
1483                     mOutputApkDataSink,
1484                     mOutputApkDataSource,
1485                     mOutputV4File,
1486                     mSigningCertificateLineage);
1487         }
1488     }
1489 }
1490