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