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