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