1 /*
2  * Copyright (C) 2018 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.internal.apk.v3;
18 
19 import static com.android.apksig.internal.apk.ApkSigningBlockUtils.encodeAsLengthPrefixedElement;
20 import static com.android.apksig.internal.apk.ApkSigningBlockUtils.encodeAsSequenceOfLengthPrefixedElements;
21 import static com.android.apksig.internal.apk.ApkSigningBlockUtils.encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes;
22 import static com.android.apksig.internal.apk.ApkSigningBlockUtils.encodeCertificates;
23 import static com.android.apksig.internal.apk.ApkSigningBlockUtils.encodePublicKey;
24 
25 import com.android.apksig.SigningCertificateLineage;
26 import com.android.apksig.internal.apk.ApkSigningBlockUtils;
27 import com.android.apksig.internal.apk.ApkSigningBlockUtils.SigningSchemeBlockAndDigests;
28 import com.android.apksig.internal.apk.ApkSigningBlockUtils.SignerConfig;
29 import com.android.apksig.internal.apk.ContentDigestAlgorithm;
30 import com.android.apksig.internal.apk.SignatureAlgorithm;
31 import com.android.apksig.internal.util.Pair;
32 import com.android.apksig.util.DataSource;
33 import com.android.apksig.util.RunnablesExecutor;
34 
35 import java.io.IOException;
36 import java.nio.ByteBuffer;
37 import java.nio.ByteOrder;
38 import java.security.InvalidKeyException;
39 import java.security.NoSuchAlgorithmException;
40 import java.security.PublicKey;
41 import java.security.SignatureException;
42 import java.security.cert.CertificateEncodingException;
43 import java.security.interfaces.ECKey;
44 import java.security.interfaces.RSAKey;
45 import java.util.ArrayList;
46 import java.util.Collections;
47 import java.util.List;
48 import java.util.Map;
49 import java.util.OptionalInt;
50 
51 /**
52  * APK Signature Scheme v3 signer.
53  *
54  * <p>APK Signature Scheme v3 builds upon APK Signature Scheme v3, and maintains all of the APK
55  * Signature Scheme v2 goals.
56  *
57  * @see <a href="https://source.android.com/security/apksigning/v2.html">APK Signature Scheme v2</a>
58  *     <p>The main contribution of APK Signature Scheme v3 is the introduction of the {@link
59  *     SigningCertificateLineage}, which enables an APK to change its signing certificate as long as
60  *     it can prove the new siging certificate was signed by the old.
61  */
62 public class V3SchemeSigner {
63     public static final int APK_SIGNATURE_SCHEME_V3_BLOCK_ID =
64             V3SchemeConstants.APK_SIGNATURE_SCHEME_V3_BLOCK_ID;
65     public static final int PROOF_OF_ROTATION_ATTR_ID = V3SchemeConstants.PROOF_OF_ROTATION_ATTR_ID;
66 
67     private final RunnablesExecutor mExecutor;
68     private final DataSource mBeforeCentralDir;
69     private final DataSource mCentralDir;
70     private final DataSource mEocd;
71     private final List<SignerConfig> mSignerConfigs;
72     private final int mBlockId;
73     private final OptionalInt mOptionalV31MinSdkVersion;
74     private final boolean mRotationTargetsDevRelease;
75 
V3SchemeSigner(DataSource beforeCentralDir, DataSource centralDir, DataSource eocd, List<SignerConfig> signerConfigs, RunnablesExecutor executor, int blockId, OptionalInt optionalV31MinSdkVersion, boolean rotationTargetsDevRelease)76     private V3SchemeSigner(DataSource beforeCentralDir,
77             DataSource centralDir,
78             DataSource eocd,
79             List<SignerConfig> signerConfigs,
80             RunnablesExecutor executor,
81             int blockId,
82             OptionalInt optionalV31MinSdkVersion,
83             boolean rotationTargetsDevRelease) {
84         mBeforeCentralDir = beforeCentralDir;
85         mCentralDir = centralDir;
86         mEocd = eocd;
87         mSignerConfigs = signerConfigs;
88         mExecutor = executor;
89         mBlockId = blockId;
90         mOptionalV31MinSdkVersion = optionalV31MinSdkVersion;
91         mRotationTargetsDevRelease = rotationTargetsDevRelease;
92     }
93 
94     /**
95      * Gets the APK Signature Scheme v3 signature algorithms to be used for signing an APK using the
96      * provided key.
97      *
98      * @param minSdkVersion minimum API Level of the platform on which the APK may be installed (see
99      *     AndroidManifest.xml minSdkVersion attribute).
100      * @throws InvalidKeyException if the provided key is not suitable for signing APKs using APK
101      *     Signature Scheme v3
102      */
getSuggestedSignatureAlgorithms(PublicKey signingKey, int minSdkVersion, boolean verityEnabled, boolean deterministicDsaSigning)103     public static List<SignatureAlgorithm> getSuggestedSignatureAlgorithms(PublicKey signingKey,
104             int minSdkVersion, boolean verityEnabled, boolean deterministicDsaSigning)
105             throws InvalidKeyException {
106         String keyAlgorithm = signingKey.getAlgorithm();
107         if ("RSA".equalsIgnoreCase(keyAlgorithm)) {
108             // Use RSASSA-PKCS1-v1_5 signature scheme instead of RSASSA-PSS to guarantee
109             // deterministic signatures which make life easier for OTA updates (fewer files
110             // changed when deterministic signature schemes are used).
111 
112             // Pick a digest which is no weaker than the key.
113             int modulusLengthBits = ((RSAKey) signingKey).getModulus().bitLength();
114             if (modulusLengthBits <= 3072) {
115                 // 3072-bit RSA is roughly 128-bit strong, meaning SHA-256 is a good fit.
116                 List<SignatureAlgorithm> algorithms = new ArrayList<>();
117                 algorithms.add(SignatureAlgorithm.RSA_PKCS1_V1_5_WITH_SHA256);
118                 if (verityEnabled) {
119                     algorithms.add(SignatureAlgorithm.VERITY_RSA_PKCS1_V1_5_WITH_SHA256);
120                 }
121                 return algorithms;
122             } else {
123                 // Keys longer than 3072 bit need to be paired with a stronger digest to avoid the
124                 // digest being the weak link. SHA-512 is the next strongest supported digest.
125                 return Collections.singletonList(SignatureAlgorithm.RSA_PKCS1_V1_5_WITH_SHA512);
126             }
127         } else if ("DSA".equalsIgnoreCase(keyAlgorithm)) {
128             // DSA is supported only with SHA-256.
129             List<SignatureAlgorithm> algorithms = new ArrayList<>();
130             algorithms.add(
131                     deterministicDsaSigning ?
132                             SignatureAlgorithm.DETDSA_WITH_SHA256 :
133                             SignatureAlgorithm.DSA_WITH_SHA256);
134             if (verityEnabled) {
135                 algorithms.add(SignatureAlgorithm.VERITY_DSA_WITH_SHA256);
136             }
137             return algorithms;
138         } else if ("EC".equalsIgnoreCase(keyAlgorithm)) {
139             // Pick a digest which is no weaker than the key.
140             int keySizeBits = ((ECKey) signingKey).getParams().getOrder().bitLength();
141             if (keySizeBits <= 256) {
142                 // 256-bit Elliptic Curve is roughly 128-bit strong, meaning SHA-256 is a good fit.
143                 List<SignatureAlgorithm> algorithms = new ArrayList<>();
144                 algorithms.add(SignatureAlgorithm.ECDSA_WITH_SHA256);
145                 if (verityEnabled) {
146                     algorithms.add(SignatureAlgorithm.VERITY_ECDSA_WITH_SHA256);
147                 }
148                 return algorithms;
149             } else {
150                 // Keys longer than 256 bit need to be paired with a stronger digest to avoid the
151                 // digest being the weak link. SHA-512 is the next strongest supported digest.
152                 return Collections.singletonList(SignatureAlgorithm.ECDSA_WITH_SHA512);
153             }
154         } else {
155             throw new InvalidKeyException("Unsupported key algorithm: " + keyAlgorithm);
156         }
157     }
158 
generateApkSignatureSchemeV3Block( RunnablesExecutor executor, DataSource beforeCentralDir, DataSource centralDir, DataSource eocd, List<SignerConfig> signerConfigs)159     public static SigningSchemeBlockAndDigests generateApkSignatureSchemeV3Block(
160             RunnablesExecutor executor,
161             DataSource beforeCentralDir,
162             DataSource centralDir,
163             DataSource eocd,
164             List<SignerConfig> signerConfigs)
165             throws IOException, InvalidKeyException, NoSuchAlgorithmException, SignatureException {
166         return new V3SchemeSigner.Builder(beforeCentralDir, centralDir, eocd, signerConfigs)
167                 .setRunnablesExecutor(executor)
168                 .setBlockId(V3SchemeConstants.APK_SIGNATURE_SCHEME_V3_BLOCK_ID)
169                 .build()
170                 .generateApkSignatureSchemeV3BlockAndDigests();
171     }
172 
generateV3SignerAttribute( SigningCertificateLineage signingCertificateLineage)173     public static byte[] generateV3SignerAttribute(
174             SigningCertificateLineage signingCertificateLineage) {
175         // FORMAT (little endian):
176         // * length-prefixed bytes: attribute pair
177         //   * uint32: ID
178         //   * bytes: value - encoded V3 SigningCertificateLineage
179         byte[] encodedLineage = signingCertificateLineage.encodeSigningCertificateLineage();
180         int payloadSize = 4 + 4 + encodedLineage.length;
181         ByteBuffer result = ByteBuffer.allocate(payloadSize);
182         result.order(ByteOrder.LITTLE_ENDIAN);
183         result.putInt(4 + encodedLineage.length);
184         result.putInt(V3SchemeConstants.PROOF_OF_ROTATION_ATTR_ID);
185         result.put(encodedLineage);
186         return result.array();
187     }
188 
generateV3RotationMinSdkVersionStrippingProtectionAttribute( int rotationMinSdkVersion)189     private static byte[] generateV3RotationMinSdkVersionStrippingProtectionAttribute(
190             int rotationMinSdkVersion) {
191         // FORMAT (little endian):
192         // * length-prefixed bytes: attribute pair
193         //   * uint32: ID
194         //   * bytes: value - int32 representing minimum SDK version for rotation
195         int payloadSize = 4 + 4 + 4;
196         ByteBuffer result = ByteBuffer.allocate(payloadSize);
197         result.order(ByteOrder.LITTLE_ENDIAN);
198         result.putInt(payloadSize - 4);
199         result.putInt(V3SchemeConstants.ROTATION_MIN_SDK_VERSION_ATTR_ID);
200         result.putInt(rotationMinSdkVersion);
201         return result.array();
202     }
203 
generateV31RotationTargetsDevReleaseAttribute()204     private static byte[] generateV31RotationTargetsDevReleaseAttribute() {
205         // FORMAT (little endian):
206         // * length-prefixed bytes: attribute pair
207         //   * uint32: ID
208         //   * bytes: value - No value is used for this attribute
209         int payloadSize = 4 + 4;
210         ByteBuffer result = ByteBuffer.allocate(payloadSize);
211         result.order(ByteOrder.LITTLE_ENDIAN);
212         result.putInt(payloadSize - 4);
213         result.putInt(V3SchemeConstants.ROTATION_ON_DEV_RELEASE_ATTR_ID);
214         return result.array();
215     }
216 
217     /**
218      * Generates and returns a new {@link SigningSchemeBlockAndDigests} containing the V3.x
219      * signing scheme block and digests based on the parameters provided to the {@link Builder}.
220      *
221      * @throws IOException if an I/O error occurs
222      * @throws NoSuchAlgorithmException if a required cryptographic algorithm implementation is
223      *         missing
224      * @throws InvalidKeyException if the X.509 encoded form of the public key cannot be obtained
225      * @throws SignatureException if an error occurs when computing digests or generating
226      *         signatures
227      */
generateApkSignatureSchemeV3BlockAndDigests()228     public SigningSchemeBlockAndDigests generateApkSignatureSchemeV3BlockAndDigests()
229             throws IOException, InvalidKeyException, NoSuchAlgorithmException, SignatureException {
230         Pair<List<SignerConfig>, Map<ContentDigestAlgorithm, byte[]>> digestInfo =
231                 ApkSigningBlockUtils.computeContentDigests(
232                         mExecutor, mBeforeCentralDir, mCentralDir, mEocd, mSignerConfigs);
233         return new SigningSchemeBlockAndDigests(
234                 generateApkSignatureSchemeV3Block(digestInfo.getSecond()), digestInfo.getSecond());
235     }
236 
generateApkSignatureSchemeV3Block( Map<ContentDigestAlgorithm, byte[]> contentDigests)237     private Pair<byte[], Integer> generateApkSignatureSchemeV3Block(
238             Map<ContentDigestAlgorithm, byte[]> contentDigests)
239             throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
240         // FORMAT:
241         // * length-prefixed sequence of length-prefixed signer blocks.
242         List<byte[]> signerBlocks = new ArrayList<>(mSignerConfigs.size());
243         int signerNumber = 0;
244         for (SignerConfig signerConfig : mSignerConfigs) {
245             signerNumber++;
246             byte[] signerBlock;
247             try {
248                 signerBlock = generateSignerBlock(signerConfig, contentDigests);
249             } catch (InvalidKeyException e) {
250                 throw new InvalidKeyException("Signer #" + signerNumber + " failed", e);
251             } catch (SignatureException e) {
252                 throw new SignatureException("Signer #" + signerNumber + " failed", e);
253             }
254             signerBlocks.add(signerBlock);
255         }
256 
257         return Pair.of(
258                 encodeAsSequenceOfLengthPrefixedElements(
259                         new byte[][] {
260                             encodeAsSequenceOfLengthPrefixedElements(signerBlocks),
261                         }),
262                 mBlockId);
263     }
264 
generateSignerBlock( SignerConfig signerConfig, Map<ContentDigestAlgorithm, byte[]> contentDigests)265     private byte[] generateSignerBlock(
266             SignerConfig signerConfig, Map<ContentDigestAlgorithm, byte[]> contentDigests)
267             throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
268         if (signerConfig.certificates.isEmpty()) {
269             throw new SignatureException("No certificates configured for signer");
270         }
271         PublicKey publicKey = signerConfig.certificates.get(0).getPublicKey();
272 
273         byte[] encodedPublicKey = encodePublicKey(publicKey);
274 
275         V3SignatureSchemeBlock.SignedData signedData = new V3SignatureSchemeBlock.SignedData();
276         try {
277             signedData.certificates = encodeCertificates(signerConfig.certificates);
278         } catch (CertificateEncodingException e) {
279             throw new SignatureException("Failed to encode certificates", e);
280         }
281 
282         List<Pair<Integer, byte[]>> digests =
283                 new ArrayList<>(signerConfig.signatureAlgorithms.size());
284         for (SignatureAlgorithm signatureAlgorithm : signerConfig.signatureAlgorithms) {
285             ContentDigestAlgorithm contentDigestAlgorithm =
286                     signatureAlgorithm.getContentDigestAlgorithm();
287             byte[] contentDigest = contentDigests.get(contentDigestAlgorithm);
288             if (contentDigest == null) {
289                 throw new RuntimeException(
290                         contentDigestAlgorithm
291                                 + " content digest for "
292                                 + signatureAlgorithm
293                                 + " not computed");
294             }
295             digests.add(Pair.of(signatureAlgorithm.getId(), contentDigest));
296         }
297         signedData.digests = digests;
298         signedData.minSdkVersion = signerConfig.minSdkVersion;
299         signedData.maxSdkVersion = signerConfig.maxSdkVersion;
300         signedData.additionalAttributes = generateAdditionalAttributes(signerConfig);
301 
302         V3SignatureSchemeBlock.Signer signer = new V3SignatureSchemeBlock.Signer();
303 
304         signer.signedData = encodeSignedData(signedData);
305 
306         signer.minSdkVersion = signerConfig.minSdkVersion;
307         signer.maxSdkVersion = signerConfig.maxSdkVersion;
308         signer.publicKey = encodedPublicKey;
309         signer.signatures =
310                 ApkSigningBlockUtils.generateSignaturesOverData(signerConfig, signer.signedData);
311 
312         return encodeSigner(signer);
313     }
314 
encodeSigner(V3SignatureSchemeBlock.Signer signer)315     private byte[] encodeSigner(V3SignatureSchemeBlock.Signer signer) {
316         byte[] signedData = encodeAsLengthPrefixedElement(signer.signedData);
317         byte[] signatures =
318                 encodeAsLengthPrefixedElement(
319                         encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes(
320                                 signer.signatures));
321         byte[] publicKey = encodeAsLengthPrefixedElement(signer.publicKey);
322 
323         // FORMAT:
324         // * length-prefixed signed data
325         // * uint32: minSdkVersion
326         // * uint32: maxSdkVersion
327         // * length-prefixed sequence of length-prefixed signatures:
328         //   * uint32: signature algorithm ID
329         //   * length-prefixed bytes: signature of signed data
330         // * length-prefixed bytes: public key (X.509 SubjectPublicKeyInfo, ASN.1 DER encoded)
331         int payloadSize = signedData.length + 4 + 4 + signatures.length + publicKey.length;
332 
333         ByteBuffer result = ByteBuffer.allocate(payloadSize);
334         result.order(ByteOrder.LITTLE_ENDIAN);
335         result.put(signedData);
336         result.putInt(signer.minSdkVersion);
337         result.putInt(signer.maxSdkVersion);
338         result.put(signatures);
339         result.put(publicKey);
340 
341         return result.array();
342     }
343 
encodeSignedData(V3SignatureSchemeBlock.SignedData signedData)344     private byte[] encodeSignedData(V3SignatureSchemeBlock.SignedData signedData) {
345         byte[] digests =
346                 encodeAsLengthPrefixedElement(
347                         encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes(
348                                 signedData.digests));
349         byte[] certs =
350                 encodeAsLengthPrefixedElement(
351                         encodeAsSequenceOfLengthPrefixedElements(signedData.certificates));
352         byte[] attributes = encodeAsLengthPrefixedElement(signedData.additionalAttributes);
353 
354         // FORMAT:
355         // * length-prefixed sequence of length-prefixed digests:
356         //   * uint32: signature algorithm ID
357         //   * length-prefixed bytes: digest of contents
358         // * length-prefixed sequence of certificates:
359         //   * length-prefixed bytes: X.509 certificate (ASN.1 DER encoded).
360         // * uint-32: minSdkVersion
361         // * uint-32: maxSdkVersion
362         // * length-prefixed sequence of length-prefixed additional attributes:
363         //   * uint32: ID
364         //   * (length - 4) bytes: value
365         //   * uint32: Proof-of-rotation ID: 0x3ba06f8c
366         //   * length-prefixed roof-of-rotation structure
367         int payloadSize = digests.length + certs.length + 4 + 4 + attributes.length;
368 
369         ByteBuffer result = ByteBuffer.allocate(payloadSize);
370         result.order(ByteOrder.LITTLE_ENDIAN);
371         result.put(digests);
372         result.put(certs);
373         result.putInt(signedData.minSdkVersion);
374         result.putInt(signedData.maxSdkVersion);
375         result.put(attributes);
376 
377         return result.array();
378     }
379 
generateAdditionalAttributes(SignerConfig signerConfig)380     private byte[] generateAdditionalAttributes(SignerConfig signerConfig) {
381         List<byte[]> attributes = new ArrayList<>();
382         if (signerConfig.signingCertificateLineage != null) {
383             attributes.add(generateV3SignerAttribute(signerConfig.signingCertificateLineage));
384         }
385         if ((mRotationTargetsDevRelease || signerConfig.signerTargetsDevRelease)
386                 && mBlockId == V3SchemeConstants.APK_SIGNATURE_SCHEME_V31_BLOCK_ID) {
387             attributes.add(generateV31RotationTargetsDevReleaseAttribute());
388         }
389         if (mOptionalV31MinSdkVersion.isPresent()
390                 && mBlockId == V3SchemeConstants.APK_SIGNATURE_SCHEME_V3_BLOCK_ID) {
391             attributes.add(generateV3RotationMinSdkVersionStrippingProtectionAttribute(
392                     mOptionalV31MinSdkVersion.getAsInt()));
393         }
394         int attributesSize = attributes.stream().mapToInt(attribute -> attribute.length).sum();
395         byte[] attributesBuffer = new byte[attributesSize];
396         if (attributesSize == 0) {
397             return new byte[0];
398         }
399         int index = 0;
400         for (byte[] attribute : attributes) {
401             System.arraycopy(attribute, 0, attributesBuffer, index, attribute.length);
402             index += attribute.length;
403         }
404         return attributesBuffer;
405     }
406 
407     private static final class V3SignatureSchemeBlock {
408         private static final class Signer {
409             public byte[] signedData;
410             public int minSdkVersion;
411             public int maxSdkVersion;
412             public List<Pair<Integer, byte[]>> signatures;
413             public byte[] publicKey;
414         }
415 
416         private static final class SignedData {
417             public List<Pair<Integer, byte[]>> digests;
418             public List<byte[]> certificates;
419             public int minSdkVersion;
420             public int maxSdkVersion;
421             public byte[] additionalAttributes;
422         }
423     }
424 
425     /** Builder of {@link V3SchemeSigner} instances. */
426     public static class Builder {
427         private final DataSource mBeforeCentralDir;
428         private final DataSource mCentralDir;
429         private final DataSource mEocd;
430         private final List<SignerConfig> mSignerConfigs;
431 
432         private RunnablesExecutor mExecutor = RunnablesExecutor.MULTI_THREADED;
433         private int mBlockId = V3SchemeConstants.APK_SIGNATURE_SCHEME_V3_BLOCK_ID;
434         private OptionalInt mOptionalV31MinSdkVersion = OptionalInt.empty();
435         private boolean mRotationTargetsDevRelease = false;
436 
437         /**
438          * Instantiates a new {@code Builder} with an APK's {@code beforeCentralDir}, {@code
439          * centralDir}, and {@code eocd}, along with a {@link List} of {@code signerConfigs} to
440          * be used to sign the APK.
441          */
Builder(DataSource beforeCentralDir, DataSource centralDir, DataSource eocd, List<SignerConfig> signerConfigs)442         public Builder(DataSource beforeCentralDir, DataSource centralDir, DataSource eocd,
443                 List<SignerConfig> signerConfigs) {
444             mBeforeCentralDir = beforeCentralDir;
445             mCentralDir = centralDir;
446             mEocd = eocd;
447             mSignerConfigs = signerConfigs;
448         }
449 
450         /**
451          * Sets the {@link RunnablesExecutor} to be used when computing the APK's content digests.
452          */
setRunnablesExecutor(RunnablesExecutor executor)453         public Builder setRunnablesExecutor(RunnablesExecutor executor) {
454             mExecutor = executor;
455             return this;
456         }
457 
458         /**
459          * Sets the {@code blockId} to be used for the V3 signature block.
460          *
461          * <p>This {@code V3SchemeSigner} currently supports the block IDs for the {@link
462          * V3SchemeConstants#APK_SIGNATURE_SCHEME_V3_BLOCK_ID v3.0} and {@link
463          * V3SchemeConstants#APK_SIGNATURE_SCHEME_V31_BLOCK_ID v3.1} signature schemes.
464          */
setBlockId(int blockId)465         public Builder setBlockId(int blockId) {
466             mBlockId = blockId;
467             return this;
468         }
469 
470         /**
471          * Sets the {@code rotationMinSdkVersion} to be written as an additional attribute in each
472          * signer's block.
473          *
474          * <p>This value provides stripping protection to ensure a v3.1 signing block with rotation
475          * is not modified or removed from the APK's signature block.
476          */
setRotationMinSdkVersion(int rotationMinSdkVersion)477         public Builder setRotationMinSdkVersion(int rotationMinSdkVersion) {
478             return setMinSdkVersionForV31(rotationMinSdkVersion);
479         }
480 
481         /**
482          * Sets the {@code minSdkVersion} to be written as an additional attribute in each
483          * signer's block.
484          *
485          * <p>This value provides the stripping protection to ensure a v3.1 signing block is not
486          * modified or removed from the APK's signature block.
487          */
setMinSdkVersionForV31(int minSdkVersion)488         public Builder setMinSdkVersionForV31(int minSdkVersion) {
489             if (minSdkVersion == V3SchemeConstants.DEV_RELEASE) {
490                 minSdkVersion = V3SchemeConstants.PROD_RELEASE;
491             }
492             mOptionalV31MinSdkVersion = OptionalInt.of(minSdkVersion);
493             return this;
494         }
495 
496         /**
497          * Sets whether the minimum SDK version of a signer is intended to target a development
498          * release; this is primarily required after the T SDK is finalized, and an APK needs to
499          * target U during its development cycle for rotation.
500          *
501          * <p>This is only required after the T SDK is finalized since S and earlier releases do
502          * not know about the V3.1 block ID, but once T is released and work begins on U, U will
503          * use the SDK version of T during development. A signer with a minimum SDK version of T's
504          * SDK version along with setting {@code enabled} to true will allow an APK to use the
505          * rotated key on a device running U while causing this to be bypassed for T.
506          *
507          * <p><em>Note:</em>If the rotation-min-sdk-version is less than or equal to 32 (Android
508          * Sv2), then the rotated signing key will be used in the v3.0 signing block and this call
509          * will be a noop.
510          */
setRotationTargetsDevRelease(boolean enabled)511         public Builder setRotationTargetsDevRelease(boolean enabled) {
512             mRotationTargetsDevRelease = enabled;
513             return this;
514         }
515 
516         /**
517          * Returns a new {@link V3SchemeSigner} built with the configuration provided to this
518          * {@code Builder}.
519          */
build()520         public V3SchemeSigner build() {
521             return new V3SchemeSigner(mBeforeCentralDir,
522                     mCentralDir,
523                     mEocd,
524                     mSignerConfigs,
525                     mExecutor,
526                     mBlockId,
527                     mOptionalV31MinSdkVersion,
528                     mRotationTargetsDevRelease);
529         }
530     }
531 }
532