1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.apksig; 18 19 import com.android.apksig.apk.ApkFormatException; 20 import com.android.apksig.util.DataSink; 21 import com.android.apksig.util.DataSource; 22 import com.android.apksig.util.RunnablesExecutor; 23 24 import java.io.Closeable; 25 import java.io.File; 26 import java.io.IOException; 27 import java.security.InvalidKeyException; 28 import java.security.NoSuchAlgorithmException; 29 import java.security.SignatureException; 30 import java.util.List; 31 import java.util.Set; 32 33 /** 34 * APK signing logic which is independent of how input and output APKs are stored, parsed, and 35 * generated. 36 * 37 * <p><h3>Operating Model</h3> 38 * 39 * The abstract operating model is that there is an input APK which is being signed, thus producing 40 * an output APK. In reality, there may be just an output APK being built from scratch, or the input 41 * APK and the output APK may be the same file. Because this engine does not deal with reading and 42 * writing files, it can handle all of these scenarios. 43 * 44 * <p>The engine is stateful and thus cannot be used for signing multiple APKs. However, once 45 * the engine signed an APK, the engine can be used to re-sign the APK after it has been modified. 46 * This may be more efficient than signing the APK using a new instance of the engine. See 47 * <a href="#incremental">Incremental Operation</a>. 48 * 49 * <p>In the engine's operating model, a signed APK is produced as follows. 50 * <ol> 51 * <li>JAR entries to be signed are output,</li> 52 * <li>JAR archive is signed using JAR signing, thus adding the so-called v1 signature to the 53 * output,</li> 54 * <li>JAR archive is signed using APK Signature Scheme v2, thus adding the so-called v2 signature 55 * to the output.</li> 56 * </ol> 57 * 58 * <p>The input APK may contain JAR entries which, depending on the engine's configuration, may or 59 * may not be output (e.g., existing signatures may need to be preserved or stripped) or which the 60 * engine will overwrite as part of signing. The engine thus offers {@link #inputJarEntry(String)} 61 * which tells the client whether the input JAR entry needs to be output. This avoids the need for 62 * the client to hard-code the aspects of APK signing which determine which parts of input must be 63 * ignored. Similarly, the engine offers {@link #inputApkSigningBlock(DataSource)} to help the 64 * client avoid dealing with preserving or stripping APK Signature Scheme v2 signature of the input 65 * APK. 66 * 67 * <p>To use the engine to sign an input APK (or a collection of JAR entries), follow these 68 * steps: 69 * <ol> 70 * <li>Obtain a new instance of the engine -- engine instances are stateful and thus cannot be used 71 * for signing multiple APKs.</li> 72 * <li>Locate the input APK's APK Signing Block and provide it to 73 * {@link #inputApkSigningBlock(DataSource)}.</li> 74 * <li>For each JAR entry in the input APK, invoke {@link #inputJarEntry(String)} to determine 75 * whether this entry should be output. The engine may request to inspect the entry.</li> 76 * <li>For each output JAR entry, invoke {@link #outputJarEntry(String)} which may request to 77 * inspect the entry.</li> 78 * <li>Once all JAR entries have been output, invoke {@link #outputJarEntries()} which may request 79 * that additional JAR entries are output. These entries comprise the output APK's JAR 80 * signature.</li> 81 * <li>Locate the ZIP Central Directory and ZIP End of Central Directory sections in the output and 82 * invoke {@link #outputZipSections2(DataSource, DataSource, DataSource)} which may request that 83 * an APK Signature Block is inserted before the ZIP Central Directory. The block contains the 84 * output APK's APK Signature Scheme v2 signature.</li> 85 * <li>Invoke {@link #outputDone()} to signal that the APK was output in full. The engine will 86 * confirm that the output APK is signed.</li> 87 * <li>Invoke {@link #close()} to signal that the engine will no longer be used. This lets the 88 * engine free any resources it no longer needs. 89 * </ol> 90 * 91 * <p>Some invocations of the engine may provide the client with a task to perform. The client is 92 * expected to perform all requested tasks before proceeding to the next stage of signing. See 93 * documentation of each method about the deadlines for performing the tasks requested by the 94 * method. 95 * 96 * <p><h3 id="incremental">Incremental Operation</h3></a> 97 * 98 * The engine supports incremental operation where a signed APK is produced, then modified and 99 * re-signed. This may be useful for IDEs, where an app is frequently re-signed after small changes 100 * by the developer. Re-signing may be more efficient than signing from scratch. 101 * 102 * <p>To use the engine in incremental mode, keep notifying the engine of changes to the APK through 103 * {@link #inputApkSigningBlock(DataSource)}, {@link #inputJarEntry(String)}, 104 * {@link #inputJarEntryRemoved(String)}, {@link #outputJarEntry(String)}, 105 * and {@link #outputJarEntryRemoved(String)}, perform the tasks requested by the engine through 106 * these methods, and, when a new signed APK is desired, run through steps 5 onwards to re-sign the 107 * APK. 108 * 109 * <p><h3>Output-only Operation</h3> 110 * 111 * The engine's abstract operating model consists of an input APK and an output APK. However, it is 112 * possible to use the engine in output-only mode where the engine's {@code input...} methods are 113 * not invoked. In this mode, the engine has less control over output because it cannot request that 114 * some JAR entries are not output. Nevertheless, the engine will attempt to make the output APK 115 * signed and will report an error if cannot do so. 116 * 117 * @see <a href="https://source.android.com/security/apksigning/index.html">Application Signing</a> 118 */ 119 public interface ApkSignerEngine extends Closeable { 120 setExecutor(RunnablesExecutor executor)121 default void setExecutor(RunnablesExecutor executor) { 122 throw new UnsupportedOperationException("setExecutor method is not implemented"); 123 } 124 125 /** 126 * Initializes the signer engine with the data already present in the apk (if any). There 127 * might already be data that can be reused if the entries has not been changed. 128 * 129 * @param manifestBytes 130 * @param entryNames 131 * @return set of entry names which were processed by the engine during the initialization, a 132 * subset of entryNames 133 */ initWith(byte[] manifestBytes, Set<String> entryNames)134 default Set<String> initWith(byte[] manifestBytes, Set<String> entryNames) { 135 throw new UnsupportedOperationException("initWith method is not implemented"); 136 } 137 138 /** 139 * Indicates to this engine that the input APK contains the provided APK Signing Block. The 140 * block may contain signatures of the input APK, such as APK Signature Scheme v2 signatures. 141 * 142 * @param apkSigningBlock APK signing block of the input APK. The provided data source is 143 * guaranteed to not be used by the engine after this method terminates. 144 * 145 * @throws IOException if an I/O error occurs while reading the APK Signing Block 146 * @throws ApkFormatException if the APK Signing Block is malformed 147 * @throws IllegalStateException if this engine is closed 148 */ inputApkSigningBlock(DataSource apkSigningBlock)149 void inputApkSigningBlock(DataSource apkSigningBlock) 150 throws IOException, ApkFormatException, IllegalStateException; 151 152 /** 153 * Indicates to this engine that the specified JAR entry was encountered in the input APK. 154 * 155 * <p>When an input entry is updated/changed, it's OK to not invoke 156 * {@link #inputJarEntryRemoved(String)} before invoking this method. 157 * 158 * @return instructions about how to proceed with this entry 159 * 160 * @throws IllegalStateException if this engine is closed 161 */ inputJarEntry(String entryName)162 InputJarEntryInstructions inputJarEntry(String entryName) throws IllegalStateException; 163 164 /** 165 * Indicates to this engine that the specified JAR entry was output. 166 * 167 * <p>It is unnecessary to invoke this method for entries added to output by this engine (e.g., 168 * requested by {@link #outputJarEntries()}) provided the entries were output with exactly the 169 * data requested by the engine. 170 * 171 * <p>When an already output entry is updated/changed, it's OK to not invoke 172 * {@link #outputJarEntryRemoved(String)} before invoking this method. 173 * 174 * @return request to inspect the entry or {@code null} if the engine does not need to inspect 175 * the entry. The request must be fulfilled before {@link #outputJarEntries()} is 176 * invoked. 177 * 178 * @throws IllegalStateException if this engine is closed 179 */ outputJarEntry(String entryName)180 InspectJarEntryRequest outputJarEntry(String entryName) throws IllegalStateException; 181 182 /** 183 * Indicates to this engine that the specified JAR entry was removed from the input. It's safe 184 * to invoke this for entries for which {@link #inputJarEntry(String)} hasn't been invoked. 185 * 186 * @return output policy of this JAR entry. The policy indicates how this input entry affects 187 * the output APK. The client of this engine should use this information to determine 188 * how the removal of this input APK's JAR entry affects the output APK. 189 * 190 * @throws IllegalStateException if this engine is closed 191 */ inputJarEntryRemoved(String entryName)192 InputJarEntryInstructions.OutputPolicy inputJarEntryRemoved(String entryName) 193 throws IllegalStateException; 194 195 /** 196 * Indicates to this engine that the specified JAR entry was removed from the output. It's safe 197 * to invoke this for entries for which {@link #outputJarEntry(String)} hasn't been invoked. 198 * 199 * @throws IllegalStateException if this engine is closed 200 */ outputJarEntryRemoved(String entryName)201 void outputJarEntryRemoved(String entryName) throws IllegalStateException; 202 203 /** 204 * Indicates to this engine that all JAR entries have been output. 205 * 206 * @return request to add JAR signature to the output or {@code null} if there is no need to add 207 * a JAR signature. The request will contain additional JAR entries to be output. The 208 * request must be fulfilled before 209 * {@link #outputZipSections2(DataSource, DataSource, DataSource)} is invoked. 210 * 211 * @throws ApkFormatException if the APK is malformed in a way which is preventing this engine 212 * from producing a valid signature. For example, if the engine uses the provided 213 * {@code META-INF/MANIFEST.MF} as a template and the file is malformed. 214 * @throws NoSuchAlgorithmException if a signature could not be generated because a required 215 * cryptographic algorithm implementation is missing 216 * @throws InvalidKeyException if a signature could not be generated because a signing key is 217 * not suitable for generating the signature 218 * @throws SignatureException if an error occurred while generating a signature 219 * @throws IllegalStateException if there are unfulfilled requests, such as to inspect some JAR 220 * entries, or if the engine is closed 221 */ outputJarEntries()222 OutputJarSignatureRequest outputJarEntries() 223 throws ApkFormatException, NoSuchAlgorithmException, InvalidKeyException, 224 SignatureException, IllegalStateException; 225 226 /** 227 * Indicates to this engine that the ZIP sections comprising the output APK have been output. 228 * 229 * <p>The provided data sources are guaranteed to not be used by the engine after this method 230 * terminates. 231 * 232 * @deprecated This is now superseded by {@link #outputZipSections2(DataSource, DataSource, 233 * DataSource)}. 234 * 235 * @param zipEntries the section of ZIP archive containing Local File Header records and data of 236 * the ZIP entries. In a well-formed archive, this section starts at the start of the 237 * archive and extends all the way to the ZIP Central Directory. 238 * @param zipCentralDirectory ZIP Central Directory section 239 * @param zipEocd ZIP End of Central Directory (EoCD) record 240 * 241 * @return request to add an APK Signing Block to the output or {@code null} if the output must 242 * not contain an APK Signing Block. The request must be fulfilled before 243 * {@link #outputDone()} is invoked. 244 * 245 * @throws IOException if an I/O error occurs while reading the provided ZIP sections 246 * @throws ApkFormatException if the provided APK is malformed in a way which prevents this 247 * engine from producing a valid signature. For example, if the APK Signing Block 248 * provided to the engine is malformed. 249 * @throws NoSuchAlgorithmException if a signature could not be generated because a required 250 * cryptographic algorithm implementation is missing 251 * @throws InvalidKeyException if a signature could not be generated because a signing key is 252 * not suitable for generating the signature 253 * @throws SignatureException if an error occurred while generating a signature 254 * @throws IllegalStateException if there are unfulfilled requests, such as to inspect some JAR 255 * entries or to output JAR signature, or if the engine is closed 256 */ 257 @Deprecated outputZipSections( DataSource zipEntries, DataSource zipCentralDirectory, DataSource zipEocd)258 OutputApkSigningBlockRequest outputZipSections( 259 DataSource zipEntries, 260 DataSource zipCentralDirectory, 261 DataSource zipEocd) 262 throws IOException, ApkFormatException, NoSuchAlgorithmException, 263 InvalidKeyException, SignatureException, IllegalStateException; 264 265 /** 266 * Indicates to this engine that the ZIP sections comprising the output APK have been output. 267 * 268 * <p>The provided data sources are guaranteed to not be used by the engine after this method 269 * terminates. 270 * 271 * @param zipEntries the section of ZIP archive containing Local File Header records and data of 272 * the ZIP entries. In a well-formed archive, this section starts at the start of the 273 * archive and extends all the way to the ZIP Central Directory. 274 * @param zipCentralDirectory ZIP Central Directory section 275 * @param zipEocd ZIP End of Central Directory (EoCD) record 276 * 277 * @return request to add an APK Signing Block to the output or {@code null} if the output must 278 * not contain an APK Signing Block. The request must be fulfilled before 279 * {@link #outputDone()} is invoked. 280 * 281 * @throws IOException if an I/O error occurs while reading the provided ZIP sections 282 * @throws ApkFormatException if the provided APK is malformed in a way which prevents this 283 * engine from producing a valid signature. For example, if the APK Signing Block 284 * provided to the engine is malformed. 285 * @throws NoSuchAlgorithmException if a signature could not be generated because a required 286 * cryptographic algorithm implementation is missing 287 * @throws InvalidKeyException if a signature could not be generated because a signing key is 288 * not suitable for generating the signature 289 * @throws SignatureException if an error occurred while generating a signature 290 * @throws IllegalStateException if there are unfulfilled requests, such as to inspect some JAR 291 * entries or to output JAR signature, or if the engine is closed 292 */ outputZipSections2( DataSource zipEntries, DataSource zipCentralDirectory, DataSource zipEocd)293 OutputApkSigningBlockRequest2 outputZipSections2( 294 DataSource zipEntries, 295 DataSource zipCentralDirectory, 296 DataSource zipEocd) 297 throws IOException, ApkFormatException, NoSuchAlgorithmException, 298 InvalidKeyException, SignatureException, IllegalStateException; 299 300 /** 301 * Indicates to this engine that the signed APK was output. 302 * 303 * <p>This does not change the output APK. The method helps the client confirm that the current 304 * output is signed. 305 * 306 * @throws IllegalStateException if there are unfulfilled requests, such as to inspect some JAR 307 * entries or to output signatures, or if the engine is closed 308 */ outputDone()309 void outputDone() throws IllegalStateException; 310 311 /** 312 * Generates a V4 signature proto and write to output file. 313 * 314 * @param data Input data to calculate a verity hash tree and hash root 315 * @param outputFile To store the serialized V4 Signature. 316 * @param ignoreFailures Whether any failures will be silently ignored. 317 * @throws InvalidKeyException if a signature could not be generated because a signing key is 318 * not suitable for generating the signature 319 * @throws NoSuchAlgorithmException if a signature could not be generated because a required 320 * cryptographic algorithm implementation is missing 321 * @throws SignatureException if an error occurred while generating a signature 322 * @throws IOException if protobuf fails to be serialized and written to file 323 */ signV4(DataSource data, File outputFile, boolean ignoreFailures)324 void signV4(DataSource data, File outputFile, boolean ignoreFailures) 325 throws InvalidKeyException, NoSuchAlgorithmException, SignatureException, IOException; 326 327 /** 328 * Checks if the signing configuration provided to the engine is capable of creating a 329 * SourceStamp. 330 */ isEligibleForSourceStamp()331 default boolean isEligibleForSourceStamp() { 332 return false; 333 } 334 335 /** Generates the digest of the certificate used to sign the source stamp. */ generateSourceStampCertificateDigest()336 default byte[] generateSourceStampCertificateDigest() throws SignatureException { 337 return new byte[0]; 338 } 339 340 /** 341 * Indicates to this engine that it will no longer be used. Invoking this on an already closed 342 * engine is OK. 343 * 344 * <p>This does not change the output APK. For example, if the output APK is not yet fully 345 * signed, it will remain so after this method terminates. 346 */ 347 @Override close()348 void close(); 349 350 /** 351 * Instructions about how to handle an input APK's JAR entry. 352 * 353 * <p>The instructions indicate whether to output the entry (see {@link #getOutputPolicy()}) and 354 * may contain a request to inspect the entry (see {@link #getInspectJarEntryRequest()}), in 355 * which case the request must be fulfilled before {@link ApkSignerEngine#outputJarEntries()} is 356 * invoked. 357 */ 358 public static class InputJarEntryInstructions { 359 private final OutputPolicy mOutputPolicy; 360 private final InspectJarEntryRequest mInspectJarEntryRequest; 361 362 /** 363 * Constructs a new {@code InputJarEntryInstructions} instance with the provided entry 364 * output policy and without a request to inspect the entry. 365 */ InputJarEntryInstructions(OutputPolicy outputPolicy)366 public InputJarEntryInstructions(OutputPolicy outputPolicy) { 367 this(outputPolicy, null); 368 } 369 370 /** 371 * Constructs a new {@code InputJarEntryInstructions} instance with the provided entry 372 * output mode and with the provided request to inspect the entry. 373 * 374 * @param inspectJarEntryRequest request to inspect the entry or {@code null} if there's no 375 * need to inspect the entry. 376 */ InputJarEntryInstructions( OutputPolicy outputPolicy, InspectJarEntryRequest inspectJarEntryRequest)377 public InputJarEntryInstructions( 378 OutputPolicy outputPolicy, 379 InspectJarEntryRequest inspectJarEntryRequest) { 380 mOutputPolicy = outputPolicy; 381 mInspectJarEntryRequest = inspectJarEntryRequest; 382 } 383 384 /** 385 * Returns the output policy for this entry. 386 */ getOutputPolicy()387 public OutputPolicy getOutputPolicy() { 388 return mOutputPolicy; 389 } 390 391 /** 392 * Returns the request to inspect the JAR entry or {@code null} if there is no need to 393 * inspect the entry. 394 */ getInspectJarEntryRequest()395 public InspectJarEntryRequest getInspectJarEntryRequest() { 396 return mInspectJarEntryRequest; 397 } 398 399 /** 400 * Output policy for an input APK's JAR entry. 401 */ 402 public static enum OutputPolicy { 403 /** Entry must not be output. */ 404 SKIP, 405 406 /** Entry should be output. */ 407 OUTPUT, 408 409 /** Entry will be output by the engine. The client can thus ignore this input entry. */ 410 OUTPUT_BY_ENGINE, 411 } 412 } 413 414 /** 415 * Request to inspect the specified JAR entry. 416 * 417 * <p>The entry's uncompressed data must be provided to the data sink returned by 418 * {@link #getDataSink()}. Once the entry's data has been provided to the sink, {@link #done()} 419 * must be invoked. 420 */ 421 interface InspectJarEntryRequest { 422 423 /** 424 * Returns the data sink into which the entry's uncompressed data should be sent. 425 */ getDataSink()426 DataSink getDataSink(); 427 428 /** 429 * Indicates that entry's data has been provided in full. 430 */ done()431 void done(); 432 433 /** 434 * Returns the name of the JAR entry. 435 */ getEntryName()436 String getEntryName(); 437 } 438 439 /** 440 * Request to add JAR signature (aka v1 signature) to the output APK. 441 * 442 * <p>Entries listed in {@link #getAdditionalJarEntries()} must be added to the output APK after 443 * which {@link #done()} must be invoked. 444 */ 445 interface OutputJarSignatureRequest { 446 447 /** 448 * Returns JAR entries that must be added to the output APK. 449 */ getAdditionalJarEntries()450 List<JarEntry> getAdditionalJarEntries(); 451 452 /** 453 * Indicates that the JAR entries contained in this request were added to the output APK. 454 */ done()455 void done(); 456 457 /** 458 * JAR entry. 459 */ 460 public static class JarEntry { 461 private final String mName; 462 private final byte[] mData; 463 464 /** 465 * Constructs a new {@code JarEntry} with the provided name and data. 466 * 467 * @param data uncompressed data of the entry. Changes to this array will not be 468 * reflected in {@link #getData()}. 469 */ JarEntry(String name, byte[] data)470 public JarEntry(String name, byte[] data) { 471 mName = name; 472 mData = data.clone(); 473 } 474 475 /** 476 * Returns the name of this ZIP entry. 477 */ getName()478 public String getName() { 479 return mName; 480 } 481 482 /** 483 * Returns the uncompressed data of this JAR entry. 484 */ getData()485 public byte[] getData() { 486 return mData.clone(); 487 } 488 } 489 } 490 491 /** 492 * Request to add the specified APK Signing Block to the output APK. APK Signature Scheme v2 493 * signature(s) of the APK are contained in this block. 494 * 495 * <p>The APK Signing Block returned by {@link #getApkSigningBlock()} must be placed into the 496 * output APK such that the block is immediately before the ZIP Central Directory, the offset of 497 * ZIP Central Directory in the ZIP End of Central Directory record must be adjusted 498 * accordingly, and then {@link #done()} must be invoked. 499 * 500 * <p>If the output contains an APK Signing Block, that block must be replaced by the block 501 * contained in this request. 502 * 503 * @deprecated This is now superseded by {@link OutputApkSigningBlockRequest2}. 504 */ 505 @Deprecated 506 interface OutputApkSigningBlockRequest { 507 508 /** 509 * Returns the APK Signing Block. 510 */ getApkSigningBlock()511 byte[] getApkSigningBlock(); 512 513 /** 514 * Indicates that the APK Signing Block was output as requested. 515 */ done()516 void done(); 517 } 518 519 /** 520 * Request to add the specified APK Signing Block to the output APK. APK Signature Scheme v2 521 * signature(s) of the APK are contained in this block. 522 * 523 * <p>The APK Signing Block returned by {@link #getApkSigningBlock()} must be placed into the 524 * output APK such that the block is immediately before the ZIP Central Directory. Immediately 525 * before the APK Signing Block must be padding consists of the number of 0x00 bytes returned by 526 * {@link #getPaddingSizeBeforeApkSigningBlock()}. The offset of ZIP Central Directory in the 527 * ZIP End of Central Directory record must be adjusted accordingly, and then {@link #done()} 528 * must be invoked. 529 * 530 * <p>If the output contains an APK Signing Block, that block must be replaced by the block 531 * contained in this request. 532 */ 533 interface OutputApkSigningBlockRequest2 { 534 /** 535 * Returns the APK Signing Block. 536 */ getApkSigningBlock()537 byte[] getApkSigningBlock(); 538 539 /** 540 * Indicates that the APK Signing Block was output as requested. 541 */ done()542 void done(); 543 544 /** 545 * Returns the number of 0x00 bytes the caller must place immediately before APK Signing 546 * Block. 547 */ getPaddingSizeBeforeApkSigningBlock()548 int getPaddingSizeBeforeApkSigningBlock(); 549 } 550 } 551