content: /*
   * 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 android.os.Build;
  
  import androidx.annotation.RequiresApi;
  
  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.IOException;
  import java.lang.UnsupportedOperationException;
  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.
   *
   * <p><h3>Operating Model</h3>
   *
   * 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.
   *
   * <p>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
   * <a href="#incremental">Incremental Operation</a>.
   *
   * <p>In the engine's operating model, a signed APK is produced as follows.
   * <ol>
   * <li>JAR entries to be signed are output,</li>
   * <li>JAR archive is signed using JAR signing, thus adding the so-called v1 signature to the
   *     output,</li>
   * <li>JAR archive is signed using APK Signature Scheme v2, thus adding the so-called v2 signature
   *     to the output.</li>
   * </ol>
   *
   * <p>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.
   *
   * <p>To use the engine to sign an input APK (or a collection of JAR entries), follow these
   * steps:
   * <ol>
   * <li>Obtain a new instance of the engine -- engine instances are stateful and thus cannot be used
   *     for signing multiple APKs.</li>
   * <li>Locate the input APK's APK Signing Block and provide it to
   *     {@link #inputApkSigningBlock(DataSource)}.</li>
   * <li>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.</li>
   * <li>For each output JAR entry, invoke {@link #outputJarEntry(String)} which may request to
   *     inspect the entry.</li>
   * <li>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.</li>
   * <li>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.</li>
   * <li>Invoke {@link #outputDone()} to signal that the APK was output in full. The engine will
   *     confirm that the output APK is signed.</li>
   * <li>Invoke {@link #close()} to signal that the engine will no longer be used. This lets the
   *     engine free any resources it no longer needs.
   * </ol>
   *
   * <p>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.
   *
   * <p><h3 id="incremental">Incremental Operation</h3></a>
   *
   * 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.
   *
   * <p>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.
   *
   * <p><h3>Output-only Operation</h3>
   *
   * 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 <a href="https://source.android.com/security/apksigning/index.html">Application Signing</a>
   */
  public interface ApkSignerEngine extends Closeable {
  
      @RequiresApi(api = Build.VERSION_CODES.N)
      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<String> initWith(byte[] manifestBytes, Set<String> 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.
       *
       * <p>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.
       *
       * <p>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.
       *
       * <p>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.
       *
       * <p>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.
       *
       * <p>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.
       *
       * <p>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;
  
      /**
       * Indicates to this engine that it will no longer be used. Invoking this on an already closed
       * engine is OK.
       *
       * <p>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.
       *
       * <p>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.
       *
       * <p>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.
       *
       * <p>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<JarEntry> 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.
       *
       * <p>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.
       *
       * <p>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.
       *
       * <p>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.
       *
       * <p>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();
      }
  }
  