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