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