/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig; import com.android.apksig.apk.ApkFormatException; import com.android.apksig.util.DataSink; import com.android.apksig.util.DataSource; import com.android.apksig.util.RunnablesExecutor; import java.io.Closeable; import java.io.File; import java.io.IOException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SignatureException; import java.util.List; import java.util.Set; /** * APK signing logic which is independent of how input and output APKs are stored, parsed, and * generated. * *

Operating Model

* * The abstract operating model is that there is an input APK which is being signed, thus producing * an output APK. In reality, there may be just an output APK being built from scratch, or the input * APK and the output APK may be the same file. Because this engine does not deal with reading and * writing files, it can handle all of these scenarios. * *

The engine is stateful and thus cannot be used for signing multiple APKs. However, once * the engine signed an APK, the engine can be used to re-sign the APK after it has been modified. * This may be more efficient than signing the APK using a new instance of the engine. See * Incremental Operation. * *

In the engine's operating model, a signed APK is produced as follows. *

    *
  1. JAR entries to be signed are output,
  2. *
  3. JAR archive is signed using JAR signing, thus adding the so-called v1 signature to the * output,
  4. *
  5. JAR archive is signed using APK Signature Scheme v2, thus adding the so-called v2 signature * to the output.
  6. *
* *

The input APK may contain JAR entries which, depending on the engine's configuration, may or * may not be output (e.g., existing signatures may need to be preserved or stripped) or which the * engine will overwrite as part of signing. The engine thus offers {@link #inputJarEntry(String)} * which tells the client whether the input JAR entry needs to be output. This avoids the need for * the client to hard-code the aspects of APK signing which determine which parts of input must be * ignored. Similarly, the engine offers {@link #inputApkSigningBlock(DataSource)} to help the * client avoid dealing with preserving or stripping APK Signature Scheme v2 signature of the input * APK. * *

To use the engine to sign an input APK (or a collection of JAR entries), follow these * steps: *

    *
  1. Obtain a new instance of the engine -- engine instances are stateful and thus cannot be used * for signing multiple APKs.
  2. *
  3. Locate the input APK's APK Signing Block and provide it to * {@link #inputApkSigningBlock(DataSource)}.
  4. *
  5. For each JAR entry in the input APK, invoke {@link #inputJarEntry(String)} to determine * whether this entry should be output. The engine may request to inspect the entry.
  6. *
  7. For each output JAR entry, invoke {@link #outputJarEntry(String)} which may request to * inspect the entry.
  8. *
  9. Once all JAR entries have been output, invoke {@link #outputJarEntries()} which may request * that additional JAR entries are output. These entries comprise the output APK's JAR * signature.
  10. *
  11. Locate the ZIP Central Directory and ZIP End of Central Directory sections in the output and * invoke {@link #outputZipSections2(DataSource, DataSource, DataSource)} which may request that * an APK Signature Block is inserted before the ZIP Central Directory. The block contains the * output APK's APK Signature Scheme v2 signature.
  12. *
  13. Invoke {@link #outputDone()} to signal that the APK was output in full. The engine will * confirm that the output APK is signed.
  14. *
  15. Invoke {@link #close()} to signal that the engine will no longer be used. This lets the * engine free any resources it no longer needs. *
* *

Some invocations of the engine may provide the client with a task to perform. The client is * expected to perform all requested tasks before proceeding to the next stage of signing. See * documentation of each method about the deadlines for performing the tasks requested by the * method. * *

Incremental Operation

* * The engine supports incremental operation where a signed APK is produced, then modified and * re-signed. This may be useful for IDEs, where an app is frequently re-signed after small changes * by the developer. Re-signing may be more efficient than signing from scratch. * *

To use the engine in incremental mode, keep notifying the engine of changes to the APK through * {@link #inputApkSigningBlock(DataSource)}, {@link #inputJarEntry(String)}, * {@link #inputJarEntryRemoved(String)}, {@link #outputJarEntry(String)}, * and {@link #outputJarEntryRemoved(String)}, perform the tasks requested by the engine through * these methods, and, when a new signed APK is desired, run through steps 5 onwards to re-sign the * APK. * *

Output-only Operation

* * The engine's abstract operating model consists of an input APK and an output APK. However, it is * possible to use the engine in output-only mode where the engine's {@code input...} methods are * not invoked. In this mode, the engine has less control over output because it cannot request that * some JAR entries are not output. Nevertheless, the engine will attempt to make the output APK * signed and will report an error if cannot do so. * * @see Application Signing */ public interface ApkSignerEngine extends Closeable { default void setExecutor(RunnablesExecutor executor) { throw new UnsupportedOperationException("setExecutor method is not implemented"); } /** * Initializes the signer engine with the data already present in the apk (if any). There * might already be data that can be reused if the entries has not been changed. * * @param manifestBytes * @param entryNames * @return set of entry names which were processed by the engine during the initialization, a * subset of entryNames */ default Set initWith(byte[] manifestBytes, Set entryNames) { throw new UnsupportedOperationException("initWith method is not implemented"); } /** * Indicates to this engine that the input APK contains the provided APK Signing Block. The * block may contain signatures of the input APK, such as APK Signature Scheme v2 signatures. * * @param apkSigningBlock APK signing block of the input APK. The provided data source is * guaranteed to not be used by the engine after this method terminates. * * @throws IOException if an I/O error occurs while reading the APK Signing Block * @throws ApkFormatException if the APK Signing Block is malformed * @throws IllegalStateException if this engine is closed */ void inputApkSigningBlock(DataSource apkSigningBlock) throws IOException, ApkFormatException, IllegalStateException; /** * Indicates to this engine that the specified JAR entry was encountered in the input APK. * *

When an input entry is updated/changed, it's OK to not invoke * {@link #inputJarEntryRemoved(String)} before invoking this method. * * @return instructions about how to proceed with this entry * * @throws IllegalStateException if this engine is closed */ InputJarEntryInstructions inputJarEntry(String entryName) throws IllegalStateException; /** * Indicates to this engine that the specified JAR entry was output. * *

It is unnecessary to invoke this method for entries added to output by this engine (e.g., * requested by {@link #outputJarEntries()}) provided the entries were output with exactly the * data requested by the engine. * *

When an already output entry is updated/changed, it's OK to not invoke * {@link #outputJarEntryRemoved(String)} before invoking this method. * * @return request to inspect the entry or {@code null} if the engine does not need to inspect * the entry. The request must be fulfilled before {@link #outputJarEntries()} is * invoked. * * @throws IllegalStateException if this engine is closed */ InspectJarEntryRequest outputJarEntry(String entryName) throws IllegalStateException; /** * Indicates to this engine that the specified JAR entry was removed from the input. It's safe * to invoke this for entries for which {@link #inputJarEntry(String)} hasn't been invoked. * * @return output policy of this JAR entry. The policy indicates how this input entry affects * the output APK. The client of this engine should use this information to determine * how the removal of this input APK's JAR entry affects the output APK. * * @throws IllegalStateException if this engine is closed */ InputJarEntryInstructions.OutputPolicy inputJarEntryRemoved(String entryName) throws IllegalStateException; /** * Indicates to this engine that the specified JAR entry was removed from the output. It's safe * to invoke this for entries for which {@link #outputJarEntry(String)} hasn't been invoked. * * @throws IllegalStateException if this engine is closed */ void outputJarEntryRemoved(String entryName) throws IllegalStateException; /** * Indicates to this engine that all JAR entries have been output. * * @return request to add JAR signature to the output or {@code null} if there is no need to add * a JAR signature. The request will contain additional JAR entries to be output. The * request must be fulfilled before * {@link #outputZipSections2(DataSource, DataSource, DataSource)} is invoked. * * @throws ApkFormatException if the APK is malformed in a way which is preventing this engine * from producing a valid signature. For example, if the engine uses the provided * {@code META-INF/MANIFEST.MF} as a template and the file is malformed. * @throws NoSuchAlgorithmException if a signature could not be generated because a required * cryptographic algorithm implementation is missing * @throws InvalidKeyException if a signature could not be generated because a signing key is * not suitable for generating the signature * @throws SignatureException if an error occurred while generating a signature * @throws IllegalStateException if there are unfulfilled requests, such as to inspect some JAR * entries, or if the engine is closed */ OutputJarSignatureRequest outputJarEntries() throws ApkFormatException, NoSuchAlgorithmException, InvalidKeyException, SignatureException, IllegalStateException; /** * Indicates to this engine that the ZIP sections comprising the output APK have been output. * *

The provided data sources are guaranteed to not be used by the engine after this method * terminates. * * @deprecated This is now superseded by {@link #outputZipSections2(DataSource, DataSource, * DataSource)}. * * @param zipEntries the section of ZIP archive containing Local File Header records and data of * the ZIP entries. In a well-formed archive, this section starts at the start of the * archive and extends all the way to the ZIP Central Directory. * @param zipCentralDirectory ZIP Central Directory section * @param zipEocd ZIP End of Central Directory (EoCD) record * * @return request to add an APK Signing Block to the output or {@code null} if the output must * not contain an APK Signing Block. The request must be fulfilled before * {@link #outputDone()} is invoked. * * @throws IOException if an I/O error occurs while reading the provided ZIP sections * @throws ApkFormatException if the provided APK is malformed in a way which prevents this * engine from producing a valid signature. For example, if the APK Signing Block * provided to the engine is malformed. * @throws NoSuchAlgorithmException if a signature could not be generated because a required * cryptographic algorithm implementation is missing * @throws InvalidKeyException if a signature could not be generated because a signing key is * not suitable for generating the signature * @throws SignatureException if an error occurred while generating a signature * @throws IllegalStateException if there are unfulfilled requests, such as to inspect some JAR * entries or to output JAR signature, or if the engine is closed */ @Deprecated OutputApkSigningBlockRequest outputZipSections( DataSource zipEntries, DataSource zipCentralDirectory, DataSource zipEocd) throws IOException, ApkFormatException, NoSuchAlgorithmException, InvalidKeyException, SignatureException, IllegalStateException; /** * Indicates to this engine that the ZIP sections comprising the output APK have been output. * *

The provided data sources are guaranteed to not be used by the engine after this method * terminates. * * @param zipEntries the section of ZIP archive containing Local File Header records and data of * the ZIP entries. In a well-formed archive, this section starts at the start of the * archive and extends all the way to the ZIP Central Directory. * @param zipCentralDirectory ZIP Central Directory section * @param zipEocd ZIP End of Central Directory (EoCD) record * * @return request to add an APK Signing Block to the output or {@code null} if the output must * not contain an APK Signing Block. The request must be fulfilled before * {@link #outputDone()} is invoked. * * @throws IOException if an I/O error occurs while reading the provided ZIP sections * @throws ApkFormatException if the provided APK is malformed in a way which prevents this * engine from producing a valid signature. For example, if the APK Signing Block * provided to the engine is malformed. * @throws NoSuchAlgorithmException if a signature could not be generated because a required * cryptographic algorithm implementation is missing * @throws InvalidKeyException if a signature could not be generated because a signing key is * not suitable for generating the signature * @throws SignatureException if an error occurred while generating a signature * @throws IllegalStateException if there are unfulfilled requests, such as to inspect some JAR * entries or to output JAR signature, or if the engine is closed */ OutputApkSigningBlockRequest2 outputZipSections2( DataSource zipEntries, DataSource zipCentralDirectory, DataSource zipEocd) throws IOException, ApkFormatException, NoSuchAlgorithmException, InvalidKeyException, SignatureException, IllegalStateException; /** * Indicates to this engine that the signed APK was output. * *

This does not change the output APK. The method helps the client confirm that the current * output is signed. * * @throws IllegalStateException if there are unfulfilled requests, such as to inspect some JAR * entries or to output signatures, or if the engine is closed */ void outputDone() throws IllegalStateException; /** * Generates a V4 signature proto and write to output file. * * @param data Input data to calculate a verity hash tree and hash root * @param outputFile To store the serialized V4 Signature. * @param ignoreFailures Whether any failures will be silently ignored. * @throws InvalidKeyException if a signature could not be generated because a signing key is * not suitable for generating the signature * @throws NoSuchAlgorithmException if a signature could not be generated because a required * cryptographic algorithm implementation is missing * @throws SignatureException if an error occurred while generating a signature * @throws IOException if protobuf fails to be serialized and written to file */ void signV4(DataSource data, File outputFile, boolean ignoreFailures) throws InvalidKeyException, NoSuchAlgorithmException, SignatureException, IOException; /** * Checks if the signing configuration provided to the engine is capable of creating a * SourceStamp. */ default boolean isEligibleForSourceStamp() { return false; } /** Generates the digest of the certificate used to sign the source stamp. */ default byte[] generateSourceStampCertificateDigest() throws SignatureException { return new byte[0]; } /** * Indicates to this engine that it will no longer be used. Invoking this on an already closed * engine is OK. * *

This does not change the output APK. For example, if the output APK is not yet fully * signed, it will remain so after this method terminates. */ @Override void close(); /** * Instructions about how to handle an input APK's JAR entry. * *

The instructions indicate whether to output the entry (see {@link #getOutputPolicy()}) and * may contain a request to inspect the entry (see {@link #getInspectJarEntryRequest()}), in * which case the request must be fulfilled before {@link ApkSignerEngine#outputJarEntries()} is * invoked. */ public static class InputJarEntryInstructions { private final OutputPolicy mOutputPolicy; private final InspectJarEntryRequest mInspectJarEntryRequest; /** * Constructs a new {@code InputJarEntryInstructions} instance with the provided entry * output policy and without a request to inspect the entry. */ public InputJarEntryInstructions(OutputPolicy outputPolicy) { this(outputPolicy, null); } /** * Constructs a new {@code InputJarEntryInstructions} instance with the provided entry * output mode and with the provided request to inspect the entry. * * @param inspectJarEntryRequest request to inspect the entry or {@code null} if there's no * need to inspect the entry. */ public InputJarEntryInstructions( OutputPolicy outputPolicy, InspectJarEntryRequest inspectJarEntryRequest) { mOutputPolicy = outputPolicy; mInspectJarEntryRequest = inspectJarEntryRequest; } /** * Returns the output policy for this entry. */ public OutputPolicy getOutputPolicy() { return mOutputPolicy; } /** * Returns the request to inspect the JAR entry or {@code null} if there is no need to * inspect the entry. */ public InspectJarEntryRequest getInspectJarEntryRequest() { return mInspectJarEntryRequest; } /** * Output policy for an input APK's JAR entry. */ public static enum OutputPolicy { /** Entry must not be output. */ SKIP, /** Entry should be output. */ OUTPUT, /** Entry will be output by the engine. The client can thus ignore this input entry. */ OUTPUT_BY_ENGINE, } } /** * Request to inspect the specified JAR entry. * *

The entry's uncompressed data must be provided to the data sink returned by * {@link #getDataSink()}. Once the entry's data has been provided to the sink, {@link #done()} * must be invoked. */ interface InspectJarEntryRequest { /** * Returns the data sink into which the entry's uncompressed data should be sent. */ DataSink getDataSink(); /** * Indicates that entry's data has been provided in full. */ void done(); /** * Returns the name of the JAR entry. */ String getEntryName(); } /** * Request to add JAR signature (aka v1 signature) to the output APK. * *

Entries listed in {@link #getAdditionalJarEntries()} must be added to the output APK after * which {@link #done()} must be invoked. */ interface OutputJarSignatureRequest { /** * Returns JAR entries that must be added to the output APK. */ List getAdditionalJarEntries(); /** * Indicates that the JAR entries contained in this request were added to the output APK. */ void done(); /** * JAR entry. */ public static class JarEntry { private final String mName; private final byte[] mData; /** * Constructs a new {@code JarEntry} with the provided name and data. * * @param data uncompressed data of the entry. Changes to this array will not be * reflected in {@link #getData()}. */ public JarEntry(String name, byte[] data) { mName = name; mData = data.clone(); } /** * Returns the name of this ZIP entry. */ public String getName() { return mName; } /** * Returns the uncompressed data of this JAR entry. */ public byte[] getData() { return mData.clone(); } } } /** * Request to add the specified APK Signing Block to the output APK. APK Signature Scheme v2 * signature(s) of the APK are contained in this block. * *

The APK Signing Block returned by {@link #getApkSigningBlock()} must be placed into the * output APK such that the block is immediately before the ZIP Central Directory, the offset of * ZIP Central Directory in the ZIP End of Central Directory record must be adjusted * accordingly, and then {@link #done()} must be invoked. * *

If the output contains an APK Signing Block, that block must be replaced by the block * contained in this request. * * @deprecated This is now superseded by {@link OutputApkSigningBlockRequest2}. */ @Deprecated interface OutputApkSigningBlockRequest { /** * Returns the APK Signing Block. */ byte[] getApkSigningBlock(); /** * Indicates that the APK Signing Block was output as requested. */ void done(); } /** * Request to add the specified APK Signing Block to the output APK. APK Signature Scheme v2 * signature(s) of the APK are contained in this block. * *

The APK Signing Block returned by {@link #getApkSigningBlock()} must be placed into the * output APK such that the block is immediately before the ZIP Central Directory. Immediately * before the APK Signing Block must be padding consists of the number of 0x00 bytes returned by * {@link #getPaddingSizeBeforeApkSigningBlock()}. The offset of ZIP Central Directory in the * ZIP End of Central Directory record must be adjusted accordingly, and then {@link #done()} * must be invoked. * *

If the output contains an APK Signing Block, that block must be replaced by the block * contained in this request. */ interface OutputApkSigningBlockRequest2 { /** * Returns the APK Signing Block. */ byte[] getApkSigningBlock(); /** * Indicates that the APK Signing Block was output as requested. */ void done(); /** * Returns the number of 0x00 bytes the caller must place immediately before APK Signing * Block. */ int getPaddingSizeBeforeApkSigningBlock(); } }