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