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.apksigner; 18 19 import static com.android.apksig.Constants.LIBRARY_PAGE_ALIGNMENT_BYTES; 20 21 import com.android.apksig.ApkSigner; 22 import com.android.apksig.ApkVerifier; 23 import com.android.apksig.SigningCertificateLineage; 24 import com.android.apksig.SigningCertificateLineage.SignerCapabilities; 25 import com.android.apksig.apk.ApkFormatException; 26 import com.android.apksig.apk.MinSdkVersionException; 27 import com.android.apksig.internal.apk.v3.V3SchemeConstants; 28 import com.android.apksig.util.DataSource; 29 import com.android.apksig.util.DataSources; 30 31 import java.io.BufferedReader; 32 import java.io.File; 33 import java.io.IOException; 34 import java.io.InputStreamReader; 35 import java.io.PrintStream; 36 import java.io.RandomAccessFile; 37 import java.nio.ByteOrder; 38 import java.nio.charset.StandardCharsets; 39 import java.nio.file.Files; 40 import java.nio.file.StandardCopyOption; 41 import java.security.MessageDigest; 42 import java.security.NoSuchAlgorithmException; 43 import java.security.Provider; 44 import java.security.PublicKey; 45 import java.security.Security; 46 import java.security.cert.CertificateEncodingException; 47 import java.security.cert.X509Certificate; 48 import java.security.interfaces.DSAKey; 49 import java.security.interfaces.DSAParams; 50 import java.security.interfaces.ECKey; 51 import java.security.interfaces.RSAKey; 52 import java.util.ArrayList; 53 import java.util.Arrays; 54 import java.util.Base64; 55 import java.util.List; 56 57 /** 58 * Command-line tool for signing APKs and for checking whether an APK's signature are expected to 59 * verify on Android devices. 60 */ 61 public class ApkSignerTool { 62 63 private static final String VERSION = "0.9"; 64 private static final String HELP_PAGE_GENERAL = "help.txt"; 65 private static final String HELP_PAGE_SIGN = "help_sign.txt"; 66 private static final String HELP_PAGE_VERIFY = "help_verify.txt"; 67 private static final String HELP_PAGE_ROTATE = "help_rotate.txt"; 68 private static final String HELP_PAGE_LINEAGE = "help_lineage.txt"; 69 private static final String BEGIN_CERTIFICATE = "-----BEGIN CERTIFICATE-----"; 70 private static final String END_CERTIFICATE = "-----END CERTIFICATE-----"; 71 72 private static MessageDigest sha256 = null; 73 private static MessageDigest sha1 = null; 74 private static MessageDigest md5 = null; 75 76 public static final int ZIP_MAGIC = 0x04034b50; 77 main(String[] params)78 public static void main(String[] params) throws Exception { 79 if ((params.length == 0) || ("--help".equals(params[0])) || ("-h".equals(params[0]))) { 80 printUsage(HELP_PAGE_GENERAL); 81 return; 82 } else if ("--version".equals(params[0])) { 83 System.out.println(VERSION); 84 return; 85 } 86 87 // BEGIN-AOSP 88 addProviders(); 89 // END-AOSP 90 91 String cmd = params[0]; 92 try { 93 if ("sign".equals(cmd)) { 94 sign(Arrays.copyOfRange(params, 1, params.length)); 95 return; 96 } else if ("verify".equals(cmd)) { 97 verify(Arrays.copyOfRange(params, 1, params.length)); 98 return; 99 } else if ("rotate".equals(cmd)) { 100 rotate(Arrays.copyOfRange(params, 1, params.length)); 101 return; 102 } else if ("lineage".equals(cmd)) { 103 lineage(Arrays.copyOfRange(params, 1, params.length)); 104 return; 105 } else if ("help".equals(cmd)) { 106 printUsage(HELP_PAGE_GENERAL); 107 return; 108 } else if ("version".equals(cmd)) { 109 System.out.println(VERSION); 110 return; 111 } else { 112 throw new ParameterException( 113 "Unsupported command: " + cmd + ". See --help for supported commands"); 114 } 115 } catch (ParameterException | OptionsParser.OptionsException e) { 116 System.err.println(e.getMessage()); 117 System.exit(1); 118 return; 119 } 120 } 121 122 // BEGIN-AOSP 123 /** 124 * Adds additional security providers to add support for signature algorithms not covered by 125 * the default providers. 126 */ addProviders()127 private static void addProviders() { 128 try { 129 Security.addProvider(new org.conscrypt.OpenSSLProvider()); 130 } catch (UnsatisfiedLinkError e) { 131 // This is expected if the library path does not include the native conscrypt library; 132 // the default providers support all but PSS algorithms. 133 } 134 } 135 // END-AOSP 136 sign(String[] params)137 private static void sign(String[] params) throws Exception { 138 if (params.length == 0) { 139 printUsage(HELP_PAGE_SIGN); 140 return; 141 } 142 143 File outputApk = null; 144 File inputApk = null; 145 boolean verbose = false; 146 boolean v1SigningEnabled = true; 147 boolean v2SigningEnabled = true; 148 boolean v3SigningEnabled = true; 149 boolean v4SigningEnabled = true; 150 boolean forceSourceStampOverwrite = false; 151 boolean sourceStampTimestampEnabled = true; 152 boolean alignFileSize = false; 153 boolean verityEnabled = false; 154 boolean debuggableApkPermitted = true; 155 boolean alignmentPreserved = false; 156 int libPageAlignment = LIBRARY_PAGE_ALIGNMENT_BYTES; 157 int minSdkVersion = 1; 158 boolean minSdkVersionSpecified = false; 159 int maxSdkVersion = Integer.MAX_VALUE; 160 int rotationMinSdkVersion = V3SchemeConstants.DEFAULT_ROTATION_MIN_SDK_VERSION; 161 boolean rotationTargetsDevRelease = false; 162 List<SignerParams> signers = new ArrayList<>(1); 163 SignerParams signerParams = new SignerParams(); 164 SigningCertificateLineage lineage = null; 165 SignerParams sourceStampSignerParams = new SignerParams(); 166 SigningCertificateLineage sourceStampLineage = null; 167 List<ProviderInstallSpec> providers = new ArrayList<>(); 168 ProviderInstallSpec providerParams = new ProviderInstallSpec(); 169 OptionsParser optionsParser = new OptionsParser(params); 170 String optionName; 171 String optionOriginalForm = null; 172 boolean v4SigningFlagFound = false; 173 boolean sourceStampFlagFound = false; 174 boolean deterministicDsaSigning = false; 175 boolean otherSignersSignaturesPreserved = false; 176 while ((optionName = optionsParser.nextOption()) != null) { 177 optionOriginalForm = optionsParser.getOptionOriginalForm(); 178 if (("help".equals(optionName)) || ("h".equals(optionName))) { 179 printUsage(HELP_PAGE_SIGN); 180 return; 181 } else if ("out".equals(optionName)) { 182 outputApk = new File(optionsParser.getRequiredValue("Output file name")); 183 } else if ("in".equals(optionName)) { 184 inputApk = new File(optionsParser.getRequiredValue("Input file name")); 185 } else if ("min-sdk-version".equals(optionName)) { 186 minSdkVersion = optionsParser.getRequiredIntValue("Mininimum API Level"); 187 minSdkVersionSpecified = true; 188 } else if ("max-sdk-version".equals(optionName)) { 189 maxSdkVersion = optionsParser.getRequiredIntValue("Maximum API Level"); 190 } else if ("rotation-min-sdk-version".equals(optionName)) { 191 rotationMinSdkVersion = optionsParser.getRequiredIntValue( 192 "Minimum API Level for Rotation"); 193 } else if ("rotation-targets-dev-release".equals(optionName)) { 194 rotationTargetsDevRelease = optionsParser.getOptionalBooleanValue(true); 195 } 196 else if ("v1-signing-enabled".equals(optionName)) { 197 v1SigningEnabled = optionsParser.getOptionalBooleanValue(true); 198 } else if ("v2-signing-enabled".equals(optionName)) { 199 v2SigningEnabled = optionsParser.getOptionalBooleanValue(true); 200 } else if ("v3-signing-enabled".equals(optionName)) { 201 v3SigningEnabled = optionsParser.getOptionalBooleanValue(true); 202 } else if ("v4-signing-enabled".equals(optionName)) { 203 v4SigningEnabled = optionsParser.getOptionalBooleanValue(true); 204 v4SigningFlagFound = true; 205 } else if ("force-stamp-overwrite".equals(optionName)) { 206 forceSourceStampOverwrite = optionsParser.getOptionalBooleanValue(true); 207 } else if ("stamp-timestamp-enabled".equals(optionName)) { 208 sourceStampTimestampEnabled = optionsParser.getOptionalBooleanValue(true); 209 } else if ("align-file-size".equals(optionName)) { 210 alignFileSize = true; 211 } else if ("verity-enabled".equals(optionName)) { 212 verityEnabled = optionsParser.getOptionalBooleanValue(true); 213 } else if ("debuggable-apk-permitted".equals(optionName)) { 214 debuggableApkPermitted = optionsParser.getOptionalBooleanValue(true); 215 } else if ("alignment-preserved".equals(optionName)) { 216 alignmentPreserved = optionsParser.getOptionalBooleanValue(true); 217 } else if ("lib-page-alignment".equals(optionName)) { 218 libPageAlignment = optionsParser.getRequiredIntValue( 219 "Native library page alignment size in bytes"); 220 } else if ("next-signer".equals(optionName)) { 221 if (!signerParams.isEmpty()) { 222 signers.add(signerParams); 223 signerParams = new SignerParams(); 224 } 225 } else if ("signer-for-min-sdk-version".equals(optionName)) { 226 if (!signerParams.isEmpty()) { 227 signers.add(signerParams); 228 signerParams = new SignerParams(); 229 } 230 signerParams.setMinSdkVersion(optionsParser.getRequiredIntValue( 231 "Mininimum API Level for signing config")); 232 } else if ("ks".equals(optionName)) { 233 signerParams.setKeystoreFile(optionsParser.getRequiredValue("KeyStore file")); 234 } else if ("ks-key-alias".equals(optionName)) { 235 signerParams.setKeystoreKeyAlias( 236 optionsParser.getRequiredValue("KeyStore key alias")); 237 } else if ("ks-pass".equals(optionName)) { 238 signerParams.setKeystorePasswordSpec( 239 optionsParser.getRequiredValue("KeyStore password")); 240 } else if ("key-pass".equals(optionName)) { 241 signerParams.setKeyPasswordSpec(optionsParser.getRequiredValue("Key password")); 242 } else if ("pass-encoding".equals(optionName)) { 243 String charsetName = 244 optionsParser.getRequiredValue("Password character encoding"); 245 try { 246 signerParams.setPasswordCharset( 247 PasswordRetriever.getCharsetByName(charsetName)); 248 } catch (IllegalArgumentException e) { 249 throw new ParameterException( 250 "Unsupported password character encoding requested using" 251 + " --pass-encoding: " + charsetName); 252 } 253 } else if ("v1-signer-name".equals(optionName)) { 254 signerParams.setV1SigFileBasename( 255 optionsParser.getRequiredValue("JAR signature file basename")); 256 } else if ("ks-type".equals(optionName)) { 257 signerParams.setKeystoreType(optionsParser.getRequiredValue("KeyStore type")); 258 } else if ("ks-provider-name".equals(optionName)) { 259 signerParams.setKeystoreProviderName( 260 optionsParser.getRequiredValue("JCA KeyStore Provider name")); 261 } else if ("ks-provider-class".equals(optionName)) { 262 signerParams.setKeystoreProviderClass( 263 optionsParser.getRequiredValue("JCA KeyStore Provider class name")); 264 } else if ("ks-provider-arg".equals(optionName)) { 265 signerParams.setKeystoreProviderArg( 266 optionsParser.getRequiredValue( 267 "JCA KeyStore Provider constructor argument")); 268 } else if ("kms-type".equals(optionName)) { 269 signerParams.setKmsType( 270 optionsParser.getRequiredValue("Key Management Service (KMS) type")); 271 } else if ("kms-key-alias".equals(optionName)) { 272 signerParams.setKmsKeyAlias( 273 optionsParser.getRequiredValue("Key Management Service (KMS) key alias")); 274 } else if ("key".equals(optionName)) { 275 signerParams.setKeyFile(optionsParser.getRequiredValue("Private key file")); 276 } else if ("cert".equals(optionName)) { 277 signerParams.setCertFile(optionsParser.getRequiredValue("Certificate file")); 278 } else if ("signer-lineage".equals(optionName)) { 279 File lineageFile = new File( 280 optionsParser.getRequiredValue("Lineage file for signing config")); 281 signerParams.setSigningCertificateLineage(getLineageFromInputFile(lineageFile)); 282 } else if ("lineage".equals(optionName)) { 283 File lineageFile = new File(optionsParser.getRequiredValue("Lineage file")); 284 lineage = getLineageFromInputFile(lineageFile); 285 } else if ("v".equals(optionName) || "verbose".equals(optionName)) { 286 verbose = optionsParser.getOptionalBooleanValue(true); 287 } else if ("next-provider".equals(optionName)) { 288 if (!providerParams.isEmpty()) { 289 providers.add(providerParams); 290 providerParams = new ProviderInstallSpec(); 291 } 292 } else if ("provider-class".equals(optionName)) { 293 providerParams.className = 294 optionsParser.getRequiredValue("JCA Provider class name"); 295 } else if ("provider-arg".equals(optionName)) { 296 providerParams.constructorParam = 297 optionsParser.getRequiredValue("JCA Provider constructor argument"); 298 } else if ("provider-pos".equals(optionName)) { 299 providerParams.position = 300 optionsParser.getRequiredIntValue("JCA Provider position"); 301 } else if ("stamp-signer".equals(optionName)) { 302 sourceStampFlagFound = true; 303 sourceStampSignerParams = processSignerParams(optionsParser); 304 } else if ("stamp-lineage".equals(optionName)) { 305 File stampLineageFile = new File( 306 optionsParser.getRequiredValue("Stamp Lineage File")); 307 sourceStampLineage = getLineageFromInputFile(stampLineageFile); 308 } else if ("deterministic-dsa-signing".equals(optionName)) { 309 deterministicDsaSigning = optionsParser.getOptionalBooleanValue(false); 310 } else if ("append-signature".equals(optionName)) { 311 otherSignersSignaturesPreserved = optionsParser.getOptionalBooleanValue(true); 312 } else { 313 throw new ParameterException( 314 "Unsupported option: " + optionOriginalForm + ". See --help for supported" 315 + " options."); 316 } 317 } 318 if (!signerParams.isEmpty()) { 319 signers.add(signerParams); 320 } 321 signerParams = null; 322 if (!providerParams.isEmpty()) { 323 providers.add(providerParams); 324 } 325 providerParams = null; 326 327 if (signers.isEmpty()) { 328 throw new ParameterException("At least one signer must be specified"); 329 } 330 331 params = optionsParser.getRemainingParams(); 332 if (inputApk != null) { 333 // Input APK has been specified via preceding parameters. We don't expect any more 334 // parameters. 335 if (params.length > 0) { 336 throw new ParameterException( 337 "Unexpected parameter(s) after " + optionOriginalForm + ": " + params[0]); 338 } 339 } else { 340 // Input APK has not been specified via preceding parameters. The next parameter is 341 // supposed to be the path to input APK. 342 if (params.length < 1) { 343 throw new ParameterException("Missing input APK"); 344 } else if (params.length > 1) { 345 throw new ParameterException( 346 "Unexpected parameter(s) after input APK (" + params[1] + ")"); 347 } 348 inputApk = new File(params[0]); 349 } 350 if ((minSdkVersionSpecified) && (minSdkVersion > maxSdkVersion)) { 351 throw new ParameterException( 352 "Min API Level (" + minSdkVersion + ") > max API Level (" + maxSdkVersion 353 + ")"); 354 } 355 356 // Install additional JCA Providers 357 for (ProviderInstallSpec providerInstallSpec : providers) { 358 providerInstallSpec.installProvider(); 359 } 360 361 ApkSigner.SignerConfig sourceStampSignerConfig = null; 362 List<ApkSigner.SignerConfig> signerConfigs = new ArrayList<>(signers.size()); 363 int signerNumber = 0; 364 try (PasswordRetriever passwordRetriever = new PasswordRetriever()) { 365 for (SignerParams signer : signers) { 366 signerNumber++; 367 signer.setName("signer #" + signerNumber); 368 ApkSigner.SignerConfig signerConfig = getSignerConfig(signer, passwordRetriever, 369 deterministicDsaSigning); 370 if (signerConfig == null) { 371 return; 372 } 373 signerConfigs.add(signerConfig); 374 } 375 if (sourceStampFlagFound) { 376 sourceStampSignerParams.setName("stamp signer"); 377 sourceStampSignerConfig = 378 getSignerConfig(sourceStampSignerParams, passwordRetriever, 379 deterministicDsaSigning); 380 if (sourceStampSignerConfig == null) { 381 return; 382 } 383 } 384 } 385 386 if (outputApk == null) { 387 outputApk = inputApk; 388 } 389 File tmpOutputApk; 390 if (inputApk.getCanonicalPath().equals(outputApk.getCanonicalPath())) { 391 tmpOutputApk = File.createTempFile("apksigner", ".apk"); 392 tmpOutputApk.deleteOnExit(); 393 } else { 394 tmpOutputApk = outputApk; 395 } 396 ApkSigner.Builder apkSignerBuilder = 397 new ApkSigner.Builder(signerConfigs) 398 .setInputApk(inputApk) 399 .setOutputApk(tmpOutputApk) 400 .setOtherSignersSignaturesPreserved(otherSignersSignaturesPreserved) 401 .setV1SigningEnabled(v1SigningEnabled) 402 .setV2SigningEnabled(v2SigningEnabled) 403 .setV3SigningEnabled(v3SigningEnabled) 404 .setV4SigningEnabled(v4SigningEnabled) 405 .setForceSourceStampOverwrite(forceSourceStampOverwrite) 406 .setSourceStampTimestampEnabled(sourceStampTimestampEnabled) 407 .setAlignFileSize(alignFileSize) 408 .setVerityEnabled(verityEnabled) 409 .setV4ErrorReportingEnabled(v4SigningEnabled && v4SigningFlagFound) 410 .setDebuggableApkPermitted(debuggableApkPermitted) 411 .setSigningCertificateLineage(lineage) 412 .setMinSdkVersionForRotation(rotationMinSdkVersion) 413 .setRotationTargetsDevRelease(rotationTargetsDevRelease) 414 .setAlignmentPreserved(alignmentPreserved) 415 .setLibraryPageAlignmentBytes(libPageAlignment); 416 if (minSdkVersionSpecified) { 417 apkSignerBuilder.setMinSdkVersion(minSdkVersion); 418 } 419 if (v4SigningEnabled) { 420 final File outputV4SignatureFile = 421 new File(outputApk.getCanonicalPath() + ".idsig"); 422 Files.deleteIfExists(outputV4SignatureFile.toPath()); 423 apkSignerBuilder.setV4SignatureOutputFile(outputV4SignatureFile); 424 } 425 if (sourceStampSignerConfig != null) { 426 apkSignerBuilder.setSourceStampSignerConfig(sourceStampSignerConfig) 427 .setSourceStampSigningCertificateLineage(sourceStampLineage); 428 } 429 ApkSigner apkSigner = apkSignerBuilder.build(); 430 try { 431 apkSigner.sign(); 432 } catch (MinSdkVersionException e) { 433 String msg = e.getMessage(); 434 if (!msg.endsWith(".")) { 435 msg += '.'; 436 } 437 throw new MinSdkVersionException( 438 "Failed to determine APK's minimum supported platform version" 439 + ". Use --min-sdk-version to override", 440 e); 441 } 442 if (!tmpOutputApk.getCanonicalPath().equals(outputApk.getCanonicalPath())) { 443 Files.move( 444 tmpOutputApk.toPath(), outputApk.toPath(), StandardCopyOption.REPLACE_EXISTING); 445 } 446 447 if (verbose) { 448 System.out.println("Signed"); 449 } 450 } 451 getSignerConfig(SignerParams signer, PasswordRetriever passwordRetriever, boolean deterministicDsaSigning)452 private static ApkSigner.SignerConfig getSignerConfig(SignerParams signer, 453 PasswordRetriever passwordRetriever, boolean deterministicDsaSigning) { 454 try { 455 signer.loadPrivateKeyAndCerts(passwordRetriever); 456 } catch (ParameterException e) { 457 System.err.println( 458 "Failed to load signer \"" + signer.getName() + "\": " + e.getMessage()); 459 System.exit(2); 460 return null; 461 } catch (Exception e) { 462 System.err.println("Failed to load signer \"" + signer.getName() + "\""); 463 e.printStackTrace(); 464 System.exit(2); 465 return null; 466 } 467 String v1SigBasename; 468 if (signer.getV1SigFileBasename() != null) { 469 v1SigBasename = signer.getV1SigFileBasename(); 470 } else if (signer.getKeystoreKeyAlias() != null) { 471 v1SigBasename = signer.getKeystoreKeyAlias(); 472 } else if (signer.getKeyFile() != null) { 473 String keyFileName = new File(signer.getKeyFile()).getName(); 474 int delimiterIndex = keyFileName.indexOf('.'); 475 if (delimiterIndex == -1) { 476 v1SigBasename = keyFileName; 477 } else { 478 v1SigBasename = keyFileName.substring(0, delimiterIndex); 479 } 480 } else if (signer.getKmsKeyAlias() != null) { 481 v1SigBasename = signer.getKmsKeyAlias(); 482 } else { 483 throw new RuntimeException("Neither KeyStore key alias nor private key file available"); 484 } 485 ApkSigner.SignerConfig.Builder signerConfigBuilder = 486 new ApkSigner.SignerConfig.Builder( 487 v1SigBasename, 488 signer.getKeyConfig(), 489 signer.getCerts(), 490 deterministicDsaSigning); 491 SigningCertificateLineage lineage = signer.getSigningCertificateLineage(); 492 int minSdkVersion = signer.getMinSdkVersion(); 493 if (minSdkVersion > 0) { 494 signerConfigBuilder.setLineageForMinSdkVersion(lineage, minSdkVersion); 495 } 496 ApkSigner.SignerConfig signerConfig = signerConfigBuilder.build(); 497 498 return signerConfig; 499 } 500 verify(String[] params)501 private static void verify(String[] params) throws Exception { 502 if (params.length == 0) { 503 printUsage(HELP_PAGE_VERIFY); 504 return; 505 } 506 507 File inputApk = null; 508 int minSdkVersion = 1; 509 boolean minSdkVersionSpecified = false; 510 int maxSdkVersion = Integer.MAX_VALUE; 511 boolean maxSdkVersionSpecified = false; 512 boolean printCerts = false; 513 boolean printCertsPem = false; 514 boolean verbose = false; 515 boolean warningsTreatedAsErrors = false; 516 boolean verifySourceStamp = false; 517 File v4SignatureFile = null; 518 OptionsParser optionsParser = new OptionsParser(params); 519 String optionName; 520 String optionOriginalForm = null; 521 String sourceCertDigest = null; 522 while ((optionName = optionsParser.nextOption()) != null) { 523 optionOriginalForm = optionsParser.getOptionOriginalForm(); 524 if ("min-sdk-version".equals(optionName)) { 525 minSdkVersion = optionsParser.getRequiredIntValue("Mininimum API Level"); 526 minSdkVersionSpecified = true; 527 } else if ("max-sdk-version".equals(optionName)) { 528 maxSdkVersion = optionsParser.getRequiredIntValue("Maximum API Level"); 529 maxSdkVersionSpecified = true; 530 } else if ("print-certs".equals(optionName)) { 531 printCerts = optionsParser.getOptionalBooleanValue(true); 532 } else if ("print-certs-pem".equals(optionName)) { 533 printCertsPem = optionsParser.getOptionalBooleanValue(true); 534 // If the PEM output of the certs is requested, this implicitly implies the 535 // cert details should be printed. 536 if (printCertsPem && !printCerts) { 537 printCerts = true; 538 } 539 } else if (("v".equals(optionName)) || ("verbose".equals(optionName))) { 540 verbose = optionsParser.getOptionalBooleanValue(true); 541 } else if ("Werr".equals(optionName)) { 542 warningsTreatedAsErrors = optionsParser.getOptionalBooleanValue(true); 543 } else if (("help".equals(optionName)) || ("h".equals(optionName))) { 544 printUsage(HELP_PAGE_VERIFY); 545 return; 546 } else if ("v4-signature-file".equals(optionName)) { 547 v4SignatureFile = new File(optionsParser.getRequiredValue( 548 "Input V4 Signature File")); 549 } else if ("in".equals(optionName)) { 550 inputApk = new File(optionsParser.getRequiredValue("Input APK file")); 551 } else if ("verify-source-stamp".equals(optionName)) { 552 verifySourceStamp = optionsParser.getOptionalBooleanValue(true); 553 } else if ("stamp-cert-digest".equals(optionName)) { 554 sourceCertDigest = optionsParser.getRequiredValue( 555 "Expected source stamp certificate digest"); 556 } else { 557 throw new ParameterException( 558 "Unsupported option: " + optionOriginalForm + ". See --help for supported" 559 + " options."); 560 } 561 } 562 params = optionsParser.getRemainingParams(); 563 564 if (inputApk != null) { 565 // Input APK has been specified in preceding parameters. We don't expect any more 566 // parameters. 567 if (params.length > 0) { 568 throw new ParameterException( 569 "Unexpected parameter(s) after " + optionOriginalForm + ": " + params[0]); 570 } 571 } else { 572 // Input APK has not been specified in preceding parameters. The next parameter is 573 // supposed to be the input APK. 574 if (params.length < 1) { 575 throw new ParameterException("Missing APK"); 576 } else if (params.length > 1) { 577 throw new ParameterException( 578 "Unexpected parameter(s) after APK (" + params[1] + ")"); 579 } 580 inputApk = new File(params[0]); 581 } 582 583 if ((minSdkVersionSpecified) && (maxSdkVersionSpecified) 584 && (minSdkVersion > maxSdkVersion)) { 585 throw new ParameterException( 586 "Min API Level (" + minSdkVersion + ") > max API Level (" + maxSdkVersion 587 + ")"); 588 } 589 590 ApkVerifier.Builder apkVerifierBuilder = new ApkVerifier.Builder(inputApk); 591 if (minSdkVersionSpecified) { 592 apkVerifierBuilder.setMinCheckedPlatformVersion(minSdkVersion); 593 } 594 if (maxSdkVersionSpecified) { 595 apkVerifierBuilder.setMaxCheckedPlatformVersion(maxSdkVersion); 596 } 597 if (v4SignatureFile != null) { 598 if (!v4SignatureFile.exists()) { 599 throw new ParameterException("V4 signature file does not exist: " 600 + v4SignatureFile.getCanonicalPath()); 601 } 602 apkVerifierBuilder.setV4SignatureFile(v4SignatureFile); 603 } 604 605 ApkVerifier apkVerifier = apkVerifierBuilder.build(); 606 ApkVerifier.Result result; 607 try { 608 result = verifySourceStamp 609 ? apkVerifier.verifySourceStamp(sourceCertDigest) 610 : apkVerifier.verify(); 611 } catch (MinSdkVersionException e) { 612 String msg = e.getMessage(); 613 if (!msg.endsWith(".")) { 614 msg += '.'; 615 } 616 throw new MinSdkVersionException( 617 "Failed to determine APK's minimum supported platform version" 618 + ". Use --min-sdk-version to override", 619 e); 620 } 621 622 boolean verified = result.isVerified(); 623 ApkVerifier.Result.SourceStampInfo sourceStampInfo = result.getSourceStampInfo(); 624 boolean warningsEncountered = false; 625 if (verified) { 626 List<X509Certificate> signerCerts = result.getSignerCertificates(); 627 if (verbose) { 628 System.out.println("Verifies"); 629 System.out.println( 630 "Verified using v1 scheme (JAR signing): " 631 + result.isVerifiedUsingV1Scheme()); 632 System.out.println( 633 "Verified using v2 scheme (APK Signature Scheme v2): " 634 + result.isVerifiedUsingV2Scheme()); 635 System.out.println( 636 "Verified using v3 scheme (APK Signature Scheme v3): " 637 + result.isVerifiedUsingV3Scheme()); 638 System.out.println( 639 "Verified using v3.1 scheme (APK Signature Scheme v3.1): " 640 + result.isVerifiedUsingV31Scheme()); 641 System.out.println( 642 "Verified using v4 scheme (APK Signature Scheme v4): " 643 + result.isVerifiedUsingV4Scheme()); 644 System.out.println("Verified for SourceStamp: " + result.isSourceStampVerified()); 645 if (!verifySourceStamp) { 646 System.out.println("Number of signers: " + signerCerts.size()); 647 } 648 } 649 if (printCerts) { 650 // The v3.1 signature scheme allows key rotation to target T+ while the original 651 // signing key can still be used with v3.0; if a v3.1 block is present then also 652 // include the target SDK versions for both rotation and the original signing key. 653 if (result.isVerifiedUsingV31Scheme()) { 654 for (ApkVerifier.Result.V3SchemeSignerInfo signer : 655 result.getV31SchemeSigners()) { 656 657 printCertificate(signer.getCertificate(), 658 "Signer (minSdkVersion=" + signer.getMinSdkVersion() 659 + (signer.getRotationTargetsDevRelease() 660 ? " (dev release=true)" : "") 661 + ", maxSdkVersion=" + signer.getMaxSdkVersion() + ")", 662 verbose, printCertsPem); 663 } 664 for (ApkVerifier.Result.V3SchemeSignerInfo signer : result.getV3SchemeSigners()) { 665 printCertificate(signer.getCertificate(), 666 "Signer (minSdkVersion=" + signer.getMinSdkVersion() 667 + ", maxSdkVersion=" + signer.getMaxSdkVersion() + ")", 668 verbose, printCertsPem); 669 } 670 } else { 671 int signerNumber = 0; 672 for (X509Certificate signerCert : signerCerts) { 673 signerNumber++; 674 printCertificate(signerCert, "Signer #" + signerNumber, verbose, 675 printCertsPem); 676 } 677 } 678 if (sourceStampInfo != null) { 679 printCertificate(sourceStampInfo.getCertificate(), "Source Stamp Signer", 680 verbose, printCertsPem); 681 } 682 } 683 if (sourceStampInfo != null && verbose) { 684 System.out.println( 685 "Source Stamp Timestamp: " + sourceStampInfo.getTimestampEpochSeconds()); 686 } 687 } else { 688 System.err.println("DOES NOT VERIFY"); 689 } 690 691 for (ApkVerifier.IssueWithParams error : result.getErrors()) { 692 System.err.println("ERROR: " + error); 693 } 694 695 @SuppressWarnings("resource") // false positive -- this resource is not opened here 696 PrintStream warningsOut = warningsTreatedAsErrors ? System.err : System.out; 697 for (ApkVerifier.IssueWithParams warning : result.getWarnings()) { 698 warningsEncountered = true; 699 warningsOut.println("WARNING: " + warning); 700 } 701 for (ApkVerifier.Result.V1SchemeSignerInfo signer : result.getV1SchemeSigners()) { 702 String signerName = signer.getName(); 703 for (ApkVerifier.IssueWithParams error : signer.getErrors()) { 704 System.err.println("ERROR: JAR signer " + signerName + ": " + error); 705 } 706 for (ApkVerifier.IssueWithParams warning : signer.getWarnings()) { 707 warningsEncountered = true; 708 warningsOut.println("WARNING: JAR signer " + signerName + ": " + warning); 709 } 710 } 711 for (ApkVerifier.Result.V2SchemeSignerInfo signer : result.getV2SchemeSigners()) { 712 String signerName = "signer #" + (signer.getIndex() + 1); 713 for (ApkVerifier.IssueWithParams error : signer.getErrors()) { 714 System.err.println( 715 "ERROR: APK Signature Scheme v2 " + signerName + ": " + error); 716 } 717 for (ApkVerifier.IssueWithParams warning : signer.getWarnings()) { 718 warningsEncountered = true; 719 warningsOut.println( 720 "WARNING: APK Signature Scheme v2 " + signerName + ": " + warning); 721 } 722 } 723 for (ApkVerifier.Result.V3SchemeSignerInfo signer : result.getV3SchemeSigners()) { 724 String signerName = "signer #" + (signer.getIndex() + 1); 725 for (ApkVerifier.IssueWithParams error : signer.getErrors()) { 726 System.err.println( 727 "ERROR: APK Signature Scheme v3 " + signerName + ": " + error); 728 } 729 for (ApkVerifier.IssueWithParams warning : signer.getWarnings()) { 730 warningsEncountered = true; 731 warningsOut.println( 732 "WARNING: APK Signature Scheme v3 " + signerName + ": " + warning); 733 } 734 } 735 for (ApkVerifier.Result.V3SchemeSignerInfo signer : result.getV31SchemeSigners()) { 736 String signerName = "signer #" + (signer.getIndex() + 1) + "(minSdkVersion=" 737 + signer.getMinSdkVersion() + ", maxSdkVersion=" + signer.getMaxSdkVersion() 738 + ")"; 739 for (ApkVerifier.IssueWithParams error : signer.getErrors()) { 740 System.err.println( 741 "ERROR: APK Signature Scheme v3.1 " + signerName + ": " + error); 742 } 743 for (ApkVerifier.IssueWithParams warning : signer.getWarnings()) { 744 warningsEncountered = true; 745 warningsOut.println( 746 "WARNING: APK Signature Scheme v3.1 " + signerName + ": " + warning); 747 } 748 } 749 750 if (sourceStampInfo != null) { 751 for (ApkVerifier.IssueWithParams error : sourceStampInfo.getErrors()) { 752 System.err.println("ERROR: SourceStamp: " + error); 753 } 754 for (ApkVerifier.IssueWithParams warning : sourceStampInfo.getWarnings()) { 755 warningsOut.println("WARNING: SourceStamp: " + warning); 756 } 757 for (ApkVerifier.IssueWithParams infoMessage : sourceStampInfo.getInfoMessages()) { 758 System.out.println("INFO: SourceStamp: " + infoMessage); 759 } 760 } 761 762 if (!verified) { 763 System.exit(1); 764 return; 765 } 766 if ((warningsTreatedAsErrors) && (warningsEncountered)) { 767 System.exit(1); 768 return; 769 } 770 } 771 rotate(String[] params)772 private static void rotate(String[] params) throws Exception { 773 if (params.length == 0) { 774 printUsage(HELP_PAGE_ROTATE); 775 return; 776 } 777 778 File outputKeyLineage = null; 779 File inputKeyLineage = null; 780 boolean verbose = false; 781 SignerParams oldSignerParams = null; 782 SignerParams newSignerParams = null; 783 int minSdkVersion = 0; 784 List<ProviderInstallSpec> providers = new ArrayList<>(); 785 ProviderInstallSpec providerParams = new ProviderInstallSpec(); 786 OptionsParser optionsParser = new OptionsParser(params); 787 String optionName; 788 String optionOriginalForm = null; 789 while ((optionName = optionsParser.nextOption()) != null) { 790 optionOriginalForm = optionsParser.getOptionOriginalForm(); 791 if (("help".equals(optionName)) || ("h".equals(optionName))) { 792 printUsage(HELP_PAGE_ROTATE); 793 return; 794 } else if ("out".equals(optionName)) { 795 outputKeyLineage = new File(optionsParser.getRequiredValue("Output file name")); 796 } else if ("in".equals(optionName)) { 797 inputKeyLineage = new File(optionsParser.getRequiredValue("Input file name")); 798 } else if ("old-signer".equals(optionName)) { 799 oldSignerParams = processSignerParams(optionsParser); 800 } else if ("new-signer".equals(optionName)) { 801 newSignerParams = processSignerParams(optionsParser); 802 } else if ("min-sdk-version".equals(optionName)) { 803 minSdkVersion = optionsParser.getRequiredIntValue("Mininimum API Level"); 804 } else if (("v".equals(optionName)) || ("verbose".equals(optionName))) { 805 verbose = optionsParser.getOptionalBooleanValue(true); 806 } else if ("next-provider".equals(optionName)) { 807 if (!providerParams.isEmpty()) { 808 providers.add(providerParams); 809 providerParams = new ProviderInstallSpec(); 810 } 811 } else if ("provider-class".equals(optionName)) { 812 providerParams.className = 813 optionsParser.getRequiredValue("JCA Provider class name"); 814 } else if ("provider-arg".equals(optionName)) { 815 providerParams.constructorParam = 816 optionsParser.getRequiredValue("JCA Provider constructor argument"); 817 } else if ("provider-pos".equals(optionName)) { 818 providerParams.position = 819 optionsParser.getRequiredIntValue("JCA Provider position"); 820 } else { 821 throw new ParameterException( 822 "Unsupported option: " + optionOriginalForm + ". See --help for supported" 823 + " options."); 824 } 825 } 826 if (!providerParams.isEmpty()) { 827 providers.add(providerParams); 828 } 829 providerParams = null; 830 831 if (oldSignerParams.isEmpty()) { 832 throw new ParameterException("Signer parameters for old signer not present"); 833 } 834 835 if (newSignerParams.isEmpty()) { 836 throw new ParameterException("Signer parameters for new signer not present"); 837 } 838 839 if (outputKeyLineage == null) { 840 throw new ParameterException("Output lineage file parameter not present"); 841 } 842 843 params = optionsParser.getRemainingParams(); 844 if (params.length > 0) { 845 throw new ParameterException( 846 "Unexpected parameter(s) after " + optionOriginalForm + ": " + params[0]); 847 } 848 849 850 // Install additional JCA Providers 851 for (ProviderInstallSpec providerInstallSpec : providers) { 852 providerInstallSpec.installProvider(); 853 } 854 855 try (PasswordRetriever passwordRetriever = new PasswordRetriever()) { 856 // populate SignerConfig for old signer 857 oldSignerParams.setName("old signer"); 858 loadPrivateKeyAndCerts(oldSignerParams, passwordRetriever); 859 SigningCertificateLineage.SignerConfig oldSignerConfig = 860 new SigningCertificateLineage.SignerConfig.Builder( 861 oldSignerParams.getKeyConfig(), 862 oldSignerParams.getCerts().get(0)) 863 .build(); 864 865 // TOOD: don't require private key 866 newSignerParams.setName("new signer"); 867 loadPrivateKeyAndCerts(newSignerParams, passwordRetriever); 868 SigningCertificateLineage.SignerConfig newSignerConfig = 869 new SigningCertificateLineage.SignerConfig.Builder( 870 newSignerParams.getKeyConfig(), 871 newSignerParams.getCerts().get(0)) 872 .build(); 873 874 // ok we're all set up, let's rotate! 875 SigningCertificateLineage lineage; 876 if (inputKeyLineage != null) { 877 // we already have history, add the new key to the end of it 878 lineage = getLineageFromInputFile(inputKeyLineage); 879 lineage.updateSignerCapabilities( 880 oldSignerConfig, oldSignerParams.getSignerCapabilitiesBuilder().build()); 881 lineage = 882 lineage.spawnDescendant( 883 oldSignerConfig, 884 newSignerConfig, 885 newSignerParams.getSignerCapabilitiesBuilder().build()); 886 } else { 887 // this is the first entry in our signing history, create a new one from the old and 888 // new signer info 889 lineage = 890 new SigningCertificateLineage.Builder(oldSignerConfig, newSignerConfig) 891 .setMinSdkVersion(minSdkVersion) 892 .setOriginalCapabilities( 893 oldSignerParams.getSignerCapabilitiesBuilder().build()) 894 .setNewCapabilities( 895 newSignerParams.getSignerCapabilitiesBuilder().build()) 896 .build(); 897 } 898 // and write out the result 899 lineage.writeToFile(outputKeyLineage); 900 } 901 if (verbose) { 902 System.out.println("Rotation entry generated."); 903 } 904 } 905 lineage(String[] params)906 public static void lineage(String[] params) throws Exception { 907 if (params.length == 0) { 908 printUsage(HELP_PAGE_LINEAGE); 909 return; 910 } 911 912 boolean verbose = false; 913 boolean printCerts = false; 914 boolean printCertsPem = false; 915 boolean lineageUpdated = false; 916 File inputKeyLineage = null; 917 File outputKeyLineage = null; 918 String optionName; 919 OptionsParser optionsParser = new OptionsParser(params); 920 List<SignerParams> signers = new ArrayList<>(1); 921 while ((optionName = optionsParser.nextOption()) != null) { 922 if (("help".equals(optionName)) || ("h".equals(optionName))) { 923 printUsage(HELP_PAGE_LINEAGE); 924 return; 925 } else if ("in".equals(optionName)) { 926 inputKeyLineage = new File(optionsParser.getRequiredValue("Input file name")); 927 } else if ("out".equals(optionName)) { 928 outputKeyLineage = new File(optionsParser.getRequiredValue("Output file name")); 929 } else if ("signer".equals(optionName)) { 930 SignerParams signerParams = processSignerParams(optionsParser); 931 signers.add(signerParams); 932 } else if (("v".equals(optionName)) || ("verbose".equals(optionName))) { 933 verbose = optionsParser.getOptionalBooleanValue(true); 934 } else if ("print-certs".equals(optionName)) { 935 printCerts = optionsParser.getOptionalBooleanValue(true); 936 } else if ("print-certs-pem".equals(optionName)) { 937 printCertsPem = optionsParser.getOptionalBooleanValue(true); 938 // If the PEM output of the certs is requested, this implicitly implies the 939 // cert details should be printed. 940 if (printCertsPem && !printCerts) { 941 printCerts = true; 942 } 943 } else { 944 throw new ParameterException( 945 "Unsupported option: " + optionsParser.getOptionOriginalForm() 946 + ". See --help for supported options."); 947 } 948 } 949 if (inputKeyLineage == null) { 950 throw new ParameterException("Input lineage file parameter not present"); 951 } 952 SigningCertificateLineage lineage = getLineageFromInputFile(inputKeyLineage); 953 954 try (PasswordRetriever passwordRetriever = new PasswordRetriever()) { 955 for (int i = 0; i < signers.size(); i++) { 956 SignerParams signerParams = signers.get(i); 957 signerParams.setName("signer #" + (i + 1)); 958 loadPrivateKeyAndCerts(signerParams, passwordRetriever); 959 SigningCertificateLineage.SignerConfig signerConfig = 960 new SigningCertificateLineage.SignerConfig.Builder( 961 signerParams.getKeyConfig(), signerParams.getCerts().get(0)) 962 .build(); 963 try { 964 // since only the caller specified capabilities will be updated a direct 965 // comparison between the original capabilities of the signer and the 966 // signerCapabilitiesBuilder object with potential default values is not 967 // possible. Instead the capabilities should be updated first, then the new 968 // capabilities can be compared against the original to determine if the 969 // lineage has been updated and needs to be written out to a file. 970 SignerCapabilities origCapabilities = lineage.getSignerCapabilities( 971 signerConfig); 972 lineage.updateSignerCapabilities( 973 signerConfig, signerParams.getSignerCapabilitiesBuilder().build()); 974 SignerCapabilities newCapabilities = lineage.getSignerCapabilities( 975 signerConfig); 976 if (origCapabilities.equals(newCapabilities)) { 977 if (verbose) { 978 System.out.println( 979 "The provided signer capabilities for " 980 + signerParams.getName() 981 + " are unchanged."); 982 } 983 } else { 984 lineageUpdated = true; 985 if (verbose) { 986 System.out.println( 987 "Updated signer capabilities for " + signerParams.getName() 988 + "."); 989 } 990 } 991 } catch (IllegalArgumentException e) { 992 throw new ParameterException( 993 "The signer " + signerParams.getName() 994 + " was not found in the specified lineage."); 995 } 996 } 997 } 998 if (printCerts) { 999 List<X509Certificate> signingCerts = lineage.getCertificatesInLineage(); 1000 for (int i = 0; i < signingCerts.size(); i++) { 1001 X509Certificate signerCert = signingCerts.get(i); 1002 SignerCapabilities signerCapabilities = lineage.getSignerCapabilities(signerCert); 1003 printCertificate(signerCert, "Signer #" + (i + 1) + " in lineage", verbose, 1004 printCertsPem); 1005 printCapabilities(signerCapabilities); 1006 } 1007 } 1008 if (lineageUpdated) { 1009 if (outputKeyLineage != null) { 1010 lineage.writeToFile(outputKeyLineage); 1011 if (verbose) { 1012 System.out.println("Updated lineage saved to " + outputKeyLineage + "."); 1013 } 1014 } else { 1015 throw new ParameterException( 1016 "The lineage was modified but an output file for the lineage was not " 1017 + "specified"); 1018 } 1019 } 1020 } 1021 1022 /** 1023 * Extracts the Signing Certificate Lineage from the provided lineage or APK file. 1024 */ getLineageFromInputFile(File inputLineageFile)1025 private static SigningCertificateLineage getLineageFromInputFile(File inputLineageFile) 1026 throws ParameterException { 1027 try (RandomAccessFile f = new RandomAccessFile(inputLineageFile, "r")) { 1028 if (f.length() < 4) { 1029 throw new ParameterException("The input file is not a valid lineage file."); 1030 } 1031 DataSource apk = DataSources.asDataSource(f); 1032 int magicValue = apk.getByteBuffer(0, 4).order(ByteOrder.LITTLE_ENDIAN).getInt(); 1033 if (magicValue == SigningCertificateLineage.MAGIC) { 1034 return SigningCertificateLineage.readFromFile(inputLineageFile); 1035 } else if (magicValue == ZIP_MAGIC) { 1036 return SigningCertificateLineage.readFromApkFile(inputLineageFile); 1037 } else { 1038 throw new ParameterException("The input file is not a valid lineage file."); 1039 } 1040 } catch (IOException | ApkFormatException | IllegalArgumentException e) { 1041 throw new ParameterException(e.getMessage()); 1042 } 1043 } 1044 processSignerParams(OptionsParser optionsParser)1045 private static SignerParams processSignerParams(OptionsParser optionsParser) 1046 throws OptionsParser.OptionsException, ParameterException { 1047 SignerParams signerParams = new SignerParams(); 1048 String optionName; 1049 while ((optionName = optionsParser.nextOption()) != null) { 1050 if ("ks".equals(optionName)) { 1051 signerParams.setKeystoreFile(optionsParser.getRequiredValue("KeyStore file")); 1052 } else if ("ks-key-alias".equals(optionName)) { 1053 signerParams.setKeystoreKeyAlias( 1054 optionsParser.getRequiredValue("KeyStore key alias")); 1055 } else if ("ks-pass".equals(optionName)) { 1056 signerParams.setKeystorePasswordSpec( 1057 optionsParser.getRequiredValue("KeyStore password")); 1058 } else if ("key-pass".equals(optionName)) { 1059 signerParams.setKeyPasswordSpec(optionsParser.getRequiredValue("Key password")); 1060 } else if ("pass-encoding".equals(optionName)) { 1061 String charsetName = 1062 optionsParser.getRequiredValue("Password character encoding"); 1063 try { 1064 signerParams.setPasswordCharset( 1065 PasswordRetriever.getCharsetByName(charsetName)); 1066 } catch (IllegalArgumentException e) { 1067 throw new ParameterException( 1068 "Unsupported password character encoding requested using" 1069 + " --pass-encoding: " + charsetName); 1070 } 1071 } else if ("ks-type".equals(optionName)) { 1072 signerParams.setKeystoreType(optionsParser.getRequiredValue("KeyStore type")); 1073 } else if ("ks-provider-name".equals(optionName)) { 1074 signerParams.setKeystoreProviderName( 1075 optionsParser.getRequiredValue("JCA KeyStore Provider name")); 1076 } else if ("ks-provider-class".equals(optionName)) { 1077 signerParams.setKeystoreProviderClass( 1078 optionsParser.getRequiredValue("JCA KeyStore Provider class name")); 1079 } else if ("ks-provider-arg".equals(optionName)) { 1080 signerParams.setKeystoreProviderArg( 1081 optionsParser.getRequiredValue( 1082 "JCA KeyStore Provider constructor argument")); 1083 } else if ("kms-type".equals(optionName)) { 1084 signerParams.setKmsType(optionsParser.getRequiredValue("KMS Type")); 1085 } else if ("kms-key-alias".equals(optionName)) { 1086 signerParams.setKmsKeyAlias(optionsParser.getRequiredValue("KMS Key Alias")); 1087 } else if ("key".equals(optionName)) { 1088 signerParams.setKeyFile(optionsParser.getRequiredValue("Private key file")); 1089 } else if ("cert".equals(optionName)) { 1090 signerParams.setCertFile(optionsParser.getRequiredValue("Certificate file")); 1091 } else if ("set-installed-data".equals(optionName)) { 1092 signerParams 1093 .getSignerCapabilitiesBuilder() 1094 .setInstalledData(optionsParser.getOptionalBooleanValue(true)); 1095 } else if ("set-shared-uid".equals(optionName)) { 1096 signerParams 1097 .getSignerCapabilitiesBuilder() 1098 .setSharedUid(optionsParser.getOptionalBooleanValue(true)); 1099 } else if ("set-permission".equals(optionName)) { 1100 signerParams 1101 .getSignerCapabilitiesBuilder() 1102 .setPermission(optionsParser.getOptionalBooleanValue(true)); 1103 } else if ("set-rollback".equals(optionName)) { 1104 signerParams 1105 .getSignerCapabilitiesBuilder() 1106 .setRollback(optionsParser.getOptionalBooleanValue(true)); 1107 } else if ("set-auth".equals(optionName)) { 1108 signerParams 1109 .getSignerCapabilitiesBuilder() 1110 .setAuth(optionsParser.getOptionalBooleanValue(true)); 1111 } else { 1112 // not a signer option, reset optionsParser and let caller deal with it 1113 optionsParser.putOption(); 1114 break; 1115 } 1116 } 1117 1118 if (signerParams.isEmpty()) { 1119 throw new ParameterException("Signer specified without arguments"); 1120 } 1121 return signerParams; 1122 } 1123 printUsage(String page)1124 private static void printUsage(String page) { 1125 try (BufferedReader in = 1126 new BufferedReader( 1127 new InputStreamReader( 1128 ApkSignerTool.class.getResourceAsStream(page), 1129 StandardCharsets.UTF_8))) { 1130 String line; 1131 while ((line = in.readLine()) != null) { 1132 System.out.println(line); 1133 } 1134 } catch (IOException e) { 1135 throw new RuntimeException("Failed to read " + page + " resource"); 1136 } 1137 } 1138 1139 /** 1140 * @see #printCertificate(X509Certificate, String, boolean, boolean) 1141 */ printCertificate(X509Certificate cert, String name, boolean verbose)1142 public static void printCertificate(X509Certificate cert, String name, boolean verbose) 1143 throws NoSuchAlgorithmException, CertificateEncodingException { 1144 printCertificate(cert, name, verbose, false); 1145 } 1146 1147 /** 1148 * Prints details from the provided certificate to stdout. 1149 * 1150 * @param cert the certificate to be displayed. 1151 * @param name the name to be used to identify the certificate. 1152 * @param verbose boolean indicating whether public key details from the certificate should be 1153 * displayed. 1154 * @param pemOutput boolean indicating whether the PEM encoding of the certificate should be 1155 * displayed. 1156 * @throws NoSuchAlgorithmException if an instance of MD5, SHA-1, or SHA-256 cannot be 1157 * obtained. 1158 * @throws CertificateEncodingException if an error is encountered when encoding the 1159 * certificate. 1160 */ printCertificate(X509Certificate cert, String name, boolean verbose, boolean pemOutput)1161 public static void printCertificate(X509Certificate cert, String name, boolean verbose, 1162 boolean pemOutput) throws NoSuchAlgorithmException, CertificateEncodingException { 1163 if (cert == null) { 1164 throw new NullPointerException("cert == null"); 1165 } 1166 if (sha256 == null || sha1 == null || md5 == null) { 1167 sha256 = MessageDigest.getInstance("SHA-256"); 1168 sha1 = MessageDigest.getInstance("SHA-1"); 1169 md5 = MessageDigest.getInstance("MD5"); 1170 } 1171 System.out.println(name + " certificate DN: " + cert.getSubjectDN()); 1172 byte[] encodedCert = cert.getEncoded(); 1173 System.out.println(name + " certificate SHA-256 digest: " + HexEncoding.encode( 1174 sha256.digest(encodedCert))); 1175 System.out.println(name + " certificate SHA-1 digest: " + HexEncoding.encode( 1176 sha1.digest(encodedCert))); 1177 System.out.println( 1178 name + " certificate MD5 digest: " + HexEncoding.encode(md5.digest(encodedCert))); 1179 if (verbose) { 1180 PublicKey publicKey = cert.getPublicKey(); 1181 System.out.println(name + " key algorithm: " + publicKey.getAlgorithm()); 1182 int keySize = -1; 1183 if (publicKey instanceof RSAKey) { 1184 keySize = ((RSAKey) publicKey).getModulus().bitLength(); 1185 } else if (publicKey instanceof ECKey) { 1186 keySize = ((ECKey) publicKey).getParams() 1187 .getOrder().bitLength(); 1188 } else if (publicKey instanceof DSAKey) { 1189 // DSA parameters may be inherited from the certificate. We 1190 // don't handle this case at the moment. 1191 DSAParams dsaParams = ((DSAKey) publicKey).getParams(); 1192 if (dsaParams != null) { 1193 keySize = dsaParams.getP().bitLength(); 1194 } 1195 } 1196 System.out.println( 1197 name + " key size (bits): " + ((keySize != -1) ? String.valueOf(keySize) 1198 : "n/a")); 1199 byte[] encodedKey = publicKey.getEncoded(); 1200 System.out.println(name + " public key SHA-256 digest: " + HexEncoding.encode( 1201 sha256.digest(encodedKey))); 1202 System.out.println(name + " public key SHA-1 digest: " + HexEncoding.encode( 1203 sha1.digest(encodedKey))); 1204 System.out.println( 1205 name + " public key MD5 digest: " + HexEncoding.encode(md5.digest(encodedKey))); 1206 } 1207 1208 if (pemOutput) { 1209 System.out.println(BEGIN_CERTIFICATE); 1210 final int lineWidth = 64; 1211 String pemEncodedCert = Base64.getEncoder().encodeToString(cert.getEncoded()); 1212 for (int i = 0; i < pemEncodedCert.length(); i += lineWidth) { 1213 System.out.println(pemEncodedCert.substring(i, i + lineWidth > pemEncodedCert.length() 1214 ? pemEncodedCert.length() 1215 : i + lineWidth)); 1216 } 1217 System.out.println(END_CERTIFICATE); 1218 } 1219 } 1220 1221 /** 1222 * Prints the capabilities of the provided object to stdout. Each of the potential 1223 * capabilities is displayed along with a boolean indicating whether this object has 1224 * that capability. 1225 */ printCapabilities(SignerCapabilities capabilities)1226 public static void printCapabilities(SignerCapabilities capabilities) { 1227 System.out.println("Has installed data capability: " + capabilities.hasInstalledData()); 1228 System.out.println("Has shared UID capability : " + capabilities.hasSharedUid()); 1229 System.out.println("Has permission capability : " + capabilities.hasPermission()); 1230 System.out.println("Has rollback capability : " + capabilities.hasRollback()); 1231 System.out.println("Has auth capability : " + capabilities.hasAuth()); 1232 } 1233 1234 private static class ProviderInstallSpec { 1235 String className; 1236 String constructorParam; 1237 Integer position; 1238 isEmpty()1239 private boolean isEmpty() { 1240 return (className == null) && (constructorParam == null) && (position == null); 1241 } 1242 installProvider()1243 private void installProvider() throws Exception { 1244 if (className == null) { 1245 throw new ParameterException( 1246 "JCA Provider class name (--provider-class) must be specified"); 1247 } 1248 1249 Class<?> providerClass = Class.forName(className); 1250 if (!Provider.class.isAssignableFrom(providerClass)) { 1251 throw new ParameterException( 1252 "JCA Provider class " + providerClass + " not subclass of " 1253 + Provider.class.getName()); 1254 } 1255 Provider provider; 1256 if (constructorParam != null) { 1257 try { 1258 // Single-arg Provider constructor 1259 provider = 1260 (Provider) providerClass.getConstructor(String.class) 1261 .newInstance(constructorParam); 1262 } catch (NoSuchMethodException e) { 1263 // Starting from JDK 9 the single-arg constructor accepting the configuration 1264 // has been replaced by a configure(String) method to be invoked after 1265 // instantiating the Provider with the no-arg constructor. 1266 provider = (Provider) providerClass.getConstructor().newInstance(); 1267 provider = (Provider) providerClass.getMethod("configure", String.class) 1268 .invoke(provider, constructorParam); 1269 } 1270 } else { 1271 // No-arg Provider constructor 1272 provider = (Provider) providerClass.getConstructor().newInstance(); 1273 } 1274 1275 if (position == null) { 1276 Security.addProvider(provider); 1277 } else { 1278 Security.insertProviderAt(provider, position); 1279 } 1280 } 1281 } 1282 1283 /** 1284 * Loads the private key and certificates from either the specified keystore or files specified 1285 * in the signer params using the provided passwordRetriever. 1286 * 1287 * @throws ParameterException if any errors are encountered when attempting to load 1288 * the private key and certificates. 1289 */ loadPrivateKeyAndCerts(SignerParams params, PasswordRetriever passwordRetriever)1290 private static void loadPrivateKeyAndCerts(SignerParams params, 1291 PasswordRetriever passwordRetriever) throws ParameterException { 1292 try { 1293 params.loadPrivateKeyAndCerts(passwordRetriever); 1294 if (params.getKeystoreKeyAlias() != null) { 1295 params.setName(params.getKeystoreKeyAlias()); 1296 } else if (params.getKeyFile() != null) { 1297 String keyFileName = new File(params.getKeyFile()).getName(); 1298 int delimiterIndex = keyFileName.indexOf('.'); 1299 if (delimiterIndex == -1) { 1300 params.setName(keyFileName); 1301 } else { 1302 params.setName(keyFileName.substring(0, delimiterIndex)); 1303 } 1304 } else { 1305 throw new RuntimeException( 1306 "Neither KeyStore key alias nor private key file available for " 1307 + params.getName()); 1308 } 1309 } catch (ParameterException e) { 1310 throw new ParameterException( 1311 "Failed to load signer \"" + params.getName() + "\":" + e.getMessage()); 1312 } catch (Exception e) { 1313 e.printStackTrace(); 1314 throw new ParameterException("Failed to load signer \"" + params.getName() + "\""); 1315 } 1316 } 1317 } 1318