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