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