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