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