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