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