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