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