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