• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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