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