Package: release.debian.org Severity: normal User: release.debian....@packages.debian.org Usertags: unblock
Please unblock package: android-platform-tools-apksig This is the next upstream release, which only fixes the password issues as described in #857027. Upstream still doesn't have release tags, hence the version string. I took this opportunity to include the bash-completion to the package as well, for completeness. Attached is the debdiff.
diff --git a/debian/apksigner.bash-completion b/debian/apksigner.bash-completion new file mode 100644 index 0000000..af6f4e3 --- /dev/null +++ b/debian/apksigner.bash-completion @@ -0,0 +1 @@ +debian/bash-completion/apksigner diff --git a/debian/bash-completion/apksigner b/debian/bash-completion/apksigner new file mode 100644 index 0000000..d68ddbe --- /dev/null +++ b/debian/bash-completion/apksigner @@ -0,0 +1,95 @@ +# Debian apksigner completion -*- shell-script -*- + +_apksigner() +{ + local cur prev words cword + _init_completion || return + + local GENERIC_OPTIONS=' + --cert + -h --help + --in + --key + --key-pass + --ks + --ks-key-alias + --ks-pass + --ks-provider-arg + --ks-provider-class + --ks-provider-name + --ks-type + --max-sdk-version + --min-sdk-version + --next-signer + --out + --print-certs + --v1-signer-name + --v1-signing-enabled + --v2-signing-enabled + -v --verbose + --Werr + ' + + # see if the user selected a command already + local COMMANDS=( + "help" + "sign" + "verify" + "version") + + local command i + for (( i=0; i < ${#words[@]}-1; i++ )); do + if [[ ${COMMANDS[@]} =~ ${words[i]} ]]; then + command=${words[i]} + break + fi + done + + # Complete a --option<SPACE><TAB> + case $prev in + --in|--out) + _filedir '@(apk|jar)' + return 0 + ;; + --ks) + _filedir '@(bks|jks|keystore)' + return 0 + ;; + esac + + # supported options per command + if [[ "$cur" == -* ]]; then + case $command in + sign|verify) + COMPREPLY=( $( compgen -W "$GENERIC_OPTIONS" -- "$cur" ) ) + return 0 + ;; + help) + return 0 + ;; + version) + return 0 + ;; + esac + fi + + # specific command arguments + if [[ -n $command ]]; then + case $command in + sign|verify) + _filedir '@(apk|jar)' + return 0 + ;; + esac + fi + + # no command yet, show what commands we have + if [ "$command" = "" ]; then + COMPREPLY=( $( compgen -W '${COMMANDS[@]}' -- "$cur" ) ) + fi + + return 0 +} && +complete -F _apksigner apksigner + +# ex: ts=4 sw=4 et filetype=sh diff --git a/debian/changelog b/debian/changelog index 7b27e37..eb91c32 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,10 @@ +android-platform-tools-apksig (0.5+git165~g42d07eb-1) unstable; urgency=medium + + * New upstream release (Closes: #857027) + * Add bash-completion + + -- Hans-Christoph Steiner <h...@eds.org> Fri, 10 Mar 2017 13:58:11 +0100 + android-platform-tools-apksig (0.4+git162~g85a854b-1) unstable; urgency=medium * New upstream release diff --git a/debian/control b/debian/control index a099c2d..3c9f8a8 100644 --- a/debian/control +++ b/debian/control @@ -4,6 +4,7 @@ Priority: optional Maintainer: Android Tools Maintainers <android-tools-de...@lists.alioth.debian.org> Uploaders: Hans-Christoph Steiner <h...@eds.org> Build-Depends: antlr3, + bash-completion, debhelper (>= 10), default-jdk-headless | default-jdk (>= 1:1.6), gradle-debian-helper, diff --git a/debian/rules b/debian/rules index 4902a7d..f4911e3 100755 --- a/debian/rules +++ b/debian/rules @@ -7,9 +7,9 @@ export JAVA_HOME=/usr/lib/jvm/default-java export CLASSPATH=/usr/share/java/apksig.jar %: - dh $@ --with maven_repo_helper,javahelper --buildsystem=gradle + dh $@ --with maven_repo_helper,javahelper,bash-completion --buildsystem=gradle -tarball_name = 85a854b038c28fa2b34eaee0ff34e67c164880ea +tarball_name = 42d07eb override_dh_auto_build: debian/apksigner.1 dh_auto_build diff --git a/src/apksigner/java/com/android/apksigner/ApkSignerTool.java b/src/apksigner/java/com/android/apksigner/ApkSignerTool.java index 745fe39..06b5603 100644 --- a/src/apksigner/java/com/android/apksigner/ApkSignerTool.java +++ b/src/apksigner/java/com/android/apksigner/ApkSignerTool.java @@ -31,9 +31,11 @@ import java.io.PrintStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.StandardCopyOption; +import java.security.InvalidKeyException; import java.security.Key; import java.security.KeyFactory; import java.security.KeyStore; +import java.security.KeyStoreException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; @@ -65,7 +67,7 @@ import javax.crypto.spec.PBEKeySpec; */ public class ApkSignerTool { - private static final String VERSION = "0.4"; + private static final String VERSION = "0.5"; private static final String HELP_PAGE_GENERAL = "help.txt"; private static final String HELP_PAGE_SIGN = "help_sign.txt"; private static final String HELP_PAGE_VERIFY = "help_verify.txt"; @@ -621,31 +623,20 @@ public class ApkSignerTool { } // 2. Load the KeyStore - char[] keystorePwd = null; + List<char[]> keystorePasswords = null; if ("NONE".equals(keystoreFile)) { ks.load(null); } else { String keystorePasswordSpec = (this.keystorePasswordSpec != null) ? this.keystorePasswordSpec : PasswordRetriever.SPEC_STDIN; - String keystorePwdString = - passwordRetriever.getPassword( + keystorePasswords = + passwordRetriever.getPasswords( keystorePasswordSpec, "Keystore password for " + name); - keystorePwd = keystorePwdString.toCharArray(); - try (FileInputStream in = new FileInputStream(keystoreFile)) { - ks.load(in, keystorePwd); - } + loadKeyStoreFromFile(ks, keystoreFile, keystorePasswords); } // 3. Load the PrivateKey and cert chain from KeyStore - char[] keyPwd; - if (keyPasswordSpec == null) { - keyPwd = keystorePwd; - } else { - keyPwd = - passwordRetriever.getPassword(keyPasswordSpec, "Key password for " + name) - .toCharArray(); - } String keyAlias = null; PrivateKey key = null; try { @@ -680,25 +671,32 @@ public class ApkSignerTool { throw new ParameterException( keystoreFile + " entry \"" + keyAlias + "\" does not contain a key"); } + Key entryKey; - if (keyPwd != null) { - // Key password specified -- load this key as a password-protected key - entryKey = ks.getKey(keyAlias, keyPwd); + if (keyPasswordSpec != null) { + // Key password spec is explicitly specified. Use this spec to obtain the + // password and then load the key using that password. + List<char[]> keyPasswords = + passwordRetriever.getPasswords( + keyPasswordSpec, + "Key \"" + keyAlias + "\" password for " + name); + entryKey = getKeyStoreKey(ks, keyAlias, keyPasswords); } else { - // Key password not specified -- try to load this key without using a password + // Key password spec is not specified. This means we should assume that key + // password is the same as the keystore password and that, if this assumption is + // wrong, we should prompt for key password and retry loading the key using that + // password. try { - entryKey = ks.getKey(keyAlias, null); + entryKey = getKeyStoreKey(ks, keyAlias, keystorePasswords); } catch (UnrecoverableKeyException expected) { - // Looks like this might be a password-protected key. Prompt for password - // and try loading the key using the password. - keyPwd = - passwordRetriever.getPassword( + List<char[]> keyPasswords = + passwordRetriever.getPasswords( PasswordRetriever.SPEC_STDIN, - "Password for key with alias \"" + keyAlias + "\"") - .toCharArray(); - entryKey = ks.getKey(keyAlias, keyPwd); + "Key \"" + keyAlias + "\" password for " + name); + entryKey = getKeyStoreKey(ks, keyAlias, keyPasswords); } } + if (entryKey == null) { throw new ParameterException( keystoreFile + " entry \"" + keyAlias + "\" does not contain a key"); @@ -727,6 +725,43 @@ public class ApkSignerTool { } } + private static void loadKeyStoreFromFile(KeyStore ks, String file, List<char[]> passwords) + throws Exception { + Exception lastFailure = null; + for (char[] password : passwords) { + try { + try (FileInputStream in = new FileInputStream(file)) { + ks.load(in, password); + } + return; + } catch (Exception e) { + lastFailure = e; + } + } + if (lastFailure == null) { + throw new RuntimeException("No keystore passwords"); + } else { + throw lastFailure; + } + } + + private static Key getKeyStoreKey(KeyStore ks, String keyAlias, List<char[]> passwords) + throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException { + UnrecoverableKeyException lastFailure = null; + for (char[] password : passwords) { + try { + return ks.getKey(keyAlias, password); + } catch (UnrecoverableKeyException e) { + lastFailure = e; + } + } + if (lastFailure == null) { + throw new RuntimeException("No key passwords"); + } else { + throw lastFailure; + } + } + private void loadPrivateKeyAndCertsFromFiles(PasswordRetriever passwordRetriver) throws Exception { if (keyFile == null) { @@ -746,15 +781,10 @@ public class ApkSignerTool { // The blob is indeed an encrypted private key blob String passwordSpec = (keyPasswordSpec != null) ? keyPasswordSpec : PasswordRetriever.SPEC_STDIN; - String keyPassword = - passwordRetriver.getPassword( + List<char[]> keyPasswords = + passwordRetriver.getPasswords( passwordSpec, "Private key password for " + name); - - PBEKeySpec decryptionKeySpec = new PBEKeySpec(keyPassword.toCharArray()); - SecretKey decryptionKey = - SecretKeyFactory.getInstance(encryptedPrivateKeyInfo.getAlgName()) - .generateSecret(decryptionKeySpec); - keySpec = encryptedPrivateKeyInfo.getKeySpec(decryptionKey); + keySpec = decryptPkcs8EncodedKey(encryptedPrivateKeyInfo, keyPasswords); } catch (IOException e) { // The blob is not an encrypted private key blob if (keyPasswordSpec == null) { @@ -787,6 +817,33 @@ public class ApkSignerTool { this.certs = certList; } + private static PKCS8EncodedKeySpec decryptPkcs8EncodedKey( + EncryptedPrivateKeyInfo encryptedPrivateKeyInfo, List<char[]> passwords) + throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { + SecretKeyFactory keyFactory = + SecretKeyFactory.getInstance(encryptedPrivateKeyInfo.getAlgName()); + InvalidKeySpecException lastKeySpecException = null; + InvalidKeyException lastKeyException = null; + for (char[] password : passwords) { + PBEKeySpec decryptionKeySpec = new PBEKeySpec(password); + try { + SecretKey decryptionKey = keyFactory.generateSecret(decryptionKeySpec); + return encryptedPrivateKeyInfo.getKeySpec(decryptionKey); + } catch (InvalidKeySpecException e) { + lastKeySpecException = e; + } catch (InvalidKeyException e) { + lastKeyException = e; + } + } + if ((lastKeyException == null) && (lastKeySpecException == null)) { + throw new RuntimeException("No passwords"); + } else if (lastKeyException != null) { + throw lastKeyException; + } else { + throw lastKeySpecException; + } + } + private static PrivateKey loadPkcs8EncodedPrivateKey(PKCS8EncodedKeySpec spec) throws InvalidKeySpecException, NoSuchAlgorithmException { try { diff --git a/src/apksigner/java/com/android/apksigner/PasswordRetriever.java b/src/apksigner/java/com/android/apksigner/PasswordRetriever.java index 25ef382..c09089d 100644 --- a/src/apksigner/java/com/android/apksigner/PasswordRetriever.java +++ b/src/apksigner/java/com/android/apksigner/PasswordRetriever.java @@ -16,14 +16,23 @@ package com.android.apksigner; -import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; import java.io.Console; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStreamReader; +import java.io.InputStream; +import java.io.PushbackInputStream; +import java.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; import java.nio.charset.Charset; -import java.nio.file.Files; +import java.nio.charset.CodingErrorAction; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -33,20 +42,23 @@ import java.util.Map; * input) which adds the need to keep some sources open across password retrievals. This class * addresses the need. * - * <p>To use this retriever, construct a new instance, use the instance to retrieve passwords, and - * then invoke {@link #clone()} on the instance when done, enabling the instance to close any - * held resources. + * <p>To use this retriever, construct a new instance, use {@link #getPasswords(String, String)} to + * retrieve passwords, and then invoke {@link #close()} on the instance when done, enabling the + * instance to release any held resources. */ class PasswordRetriever implements AutoCloseable { public static final String SPEC_STDIN = "stdin"; - private final Map<File, BufferedReader> mFileReaders = new HashMap<>(); - private BufferedReader mStdIn; + private static final Charset CONSOLE_CHARSET = getConsoleEncoding(); + + private final Map<File, InputStream> mFileInputStreams = new HashMap<>(); private boolean mClosed; /** - * Gets the password described by the provided spec. + * Returns the passwords described by the provided spec. The reason there may be more than one + * password is compatibility with {@code keytool} and {@code jarsigner} which in certain cases + * use the form of passwords encoded using the console's character encoding. * * <p>Supported specs: * <ul> @@ -61,46 +73,85 @@ class PasswordRetriever implements AutoCloseable { * <p>When the same file (including standard input) is used for providing multiple passwords, * the passwords are read from the file one line at a time. */ - public String getPassword(String spec, String description) throws IOException { + public List<char[]> getPasswords(String spec, String description) throws IOException { + // IMPLEMENTATION NOTE: Java KeyStore and PBEKeySpec APIs take passwords as arrays of + // Unicode characters (char[]). Unfortunately, it appears that Sun/Oracle keytool and + // jarsigner in some cases use passwords which are the encoded form obtained using the + // console's character encoding. For example, if the encoding is UTF-8, keytool and + // jarsigner will use the password which is obtained by upcasting each byte of the UTF-8 + // encoded form to char. This occurs only when the password is read from stdin/console, and + // does not occur when the password is read from a command-line parameter. + // There are other tools which use the Java KeyStore API correctly. + // Thus, for each password spec, there may be up to three passwords: + // * Unicode characters, + // * characters (upcast bytes) obtained from encoding the password using the console's + // character encoding, + // * characters (upcast bytes) obtained from encoding the password using the JVM's default + // character encoding. + // + // For a sample password "\u0061\u0062\u00a1\u00e4\u044e\u0031": + // On Windows 10 with English US as the UI language, IBM437 is used as console encoding and + // windows-1252 is used as the JVM default encoding: + // * keytool -genkey -v -keystore native.jks -keyalg RSA -keysize 2048 -validity 10000 + // -alias test + // generates a keystore and key which decrypt only with + // "\u0061\u0062\u00ad\u0084\u003f\u0031" + // * keytool -genkey -v -keystore native.jks -keyalg RSA -keysize 2048 -validity 10000 + // -alias test -storepass <pass here> + // generates a keystore and key which decrypt only with + // "\u0061\u0062\u00a1\u00e4\u003f\u0031" + // On modern OSX/Linux UTF-8 is used as the console and JVM default encoding: + // * keytool -genkey -v -keystore native.jks -keyalg RSA -keysize 2048 -validity 10000 + // -alias test + // generates a keystore and key which decrypt only with + // "\u0061\u0062\u00c2\u00a1\u00c3\u00a4\u00d1\u008e\u0031" + // * keytool -genkey -v -keystore native.jks -keyalg RSA -keysize 2048 -validity 10000 + // -alias test + // generates a keystore and key which decrypt only with + // "\u0061\u0062\u00a1\u00e4\u044e\u0031" + assertNotClosed(); if (spec.startsWith("pass:")) { - return spec.substring("pass:".length()); + char[] pwd = spec.substring("pass:".length()).toCharArray(); + return getPasswords(pwd); } else if (SPEC_STDIN.equals(spec)) { Console console = System.console(); if (console != null) { - char[] password = console.readPassword(description + ": "); - if (password == null) { + // Reading from console + char[] pwd = console.readPassword(description + ": "); + if (pwd == null) { throw new IOException("Failed to read " + description + ": console closed"); } - return new String(password); - } - - if (mStdIn == null) { - mStdIn = - new BufferedReader( - new InputStreamReader(System.in, Charset.defaultCharset())); - } - System.out.println(description + ":"); - String line = mStdIn.readLine(); - if (line == null) { - throw new IOException( - "Failed to read " + description + ": standard input closed"); + return getPasswords(pwd); + } else { + // Console not available -- reading from redirected input + System.out.println(description + ": "); + byte[] encodedPwd = readEncodedPassword(System.in); + if (encodedPwd.length == 0) { + throw new IOException( + "Failed to read " + description + ": standard input closed"); + } + // By default, textual input obtained via standard input is supposed to be decoded + // using the in JVM default character encoding but we also try the console's + // encoding just in case. + return getPasswords(encodedPwd, Charset.defaultCharset(), CONSOLE_CHARSET); } - return line; } else if (spec.startsWith("file:")) { String name = spec.substring("file:".length()); File file = new File(name).getCanonicalFile(); - BufferedReader in = mFileReaders.get(file); + InputStream in = mFileInputStreams.get(file); if (in == null) { - in = Files.newBufferedReader(file.toPath(), Charset.defaultCharset()); - mFileReaders.put(file, in); + in = new FileInputStream(file); + mFileInputStreams.put(file, in); } - String line = in.readLine(); - if (line == null) { + byte[] encodedPwd = readEncodedPassword(in); + if (encodedPwd.length == 0) { throw new IOException( "Failed to read " + description + " : end of file reached in " + file); } - return line; + // By default, textual input from files is supposed to be treated as encoded using JVM's + // default character encoding. + return getPasswords(encodedPwd, Charset.defaultCharset()); } else if (spec.startsWith("env:")) { String name = spec.substring("env:".length()); String value = System.getenv(name); @@ -109,12 +160,179 @@ class PasswordRetriever implements AutoCloseable { "Failed to read " + description + ": environment variable " + value + " not specified"); } - return value; + return getPasswords(value.toCharArray()); } else { throw new IOException("Unsupported password spec for " + description + ": " + spec); } } + /** + * Returns the provided password and all password variants derived from the password. The + * resulting list is guaranteed to contain at least one element. + */ + private static List<char[]> getPasswords(char[] pwd) { + List<char[]> passwords = new ArrayList<>(3); + addPasswords(passwords, pwd); + return passwords; + } + + /** + * Returns the provided password and all password variants derived from the password. The + * resulting list is guaranteed to contain at least one element. + * + * @param encodedPwd password encoded using the provided character encoding. + * @param encodings character encodings in which the password is encoded in {@code encodedPwd}. + */ + private static List<char[]> getPasswords(byte[] encodedPwd, Charset... encodings) { + List<char[]> passwords = new ArrayList<>(4); + + for (Charset encoding : encodings) { + // Decode password and add it and its variants to the list + try { + char[] pwd = decodePassword(encodedPwd, encoding); + addPasswords(passwords, pwd); + } catch (IOException ignored) {} + } + + // Add the original encoded form + addPassword(passwords, castBytesToChars(encodedPwd)); + return passwords; + } + + /** + * Adds the provided password and its variants to the provided list of passwords. + * + * <p>NOTE: This method adds only the passwords/variants which are not yet in the list. + */ + private static void addPasswords(List<char[]> passwords, char[] pwd) { + // Verbatim password + addPassword(passwords, pwd); + + // Password encoded using the JVM default character encoding and upcast into char[] + try { + char[] encodedPwd = castBytesToChars(encodePassword(pwd, Charset.defaultCharset())); + addPassword(passwords, encodedPwd); + } catch (IOException ignored) {} + + // Password encoded using console character encoding and upcast into char[] + if (!CONSOLE_CHARSET.equals(Charset.defaultCharset())) { + try { + char[] encodedPwd = castBytesToChars(encodePassword(pwd, CONSOLE_CHARSET)); + addPassword(passwords, encodedPwd); + } catch (IOException ignored) {} + } + } + + /** + * Adds the provided password to the provided list. Does nothing if the password is already in + * the list. + */ + private static void addPassword(List<char[]> passwords, char[] password) { + for (char[] existingPassword : passwords) { + if (Arrays.equals(password, existingPassword)) { + return; + } + } + passwords.add(password); + } + + private static byte[] encodePassword(char[] pwd, Charset cs) throws IOException { + ByteBuffer pwdBytes = + cs.newEncoder() + .onMalformedInput(CodingErrorAction.REPLACE) + .onUnmappableCharacter(CodingErrorAction.REPLACE) + .encode(CharBuffer.wrap(pwd)); + byte[] encoded = new byte[pwdBytes.remaining()]; + pwdBytes.get(encoded); + return encoded; + } + + private static char[] decodePassword(byte[] pwdBytes, Charset encoding) throws IOException { + CharBuffer pwdChars = + encoding.newDecoder() + .onMalformedInput(CodingErrorAction.REPLACE) + .onUnmappableCharacter(CodingErrorAction.REPLACE) + .decode(ByteBuffer.wrap(pwdBytes)); + char[] result = new char[pwdChars.remaining()]; + pwdChars.get(result); + return result; + } + + /** + * Upcasts each {@code byte} in the provided array of bytes to a {@code char} and returns the + * resulting array of characters. + */ + private static char[] castBytesToChars(byte[] bytes) { + if (bytes == null) { + return null; + } + + char[] chars = new char[bytes.length]; + for (int i = 0; i < bytes.length; i++) { + chars[i] = (char) (bytes[i] & 0xff); + } + return chars; + } + + /** + * Returns the character encoding used by the console. + */ + private static Charset getConsoleEncoding() { + // IMPLEMENTATION NOTE: There is no public API for obtaining the console's character + // encoding. We thus cheat by using implementation details of the most popular JVMs. + String consoleCharsetName; + try { + Method encodingMethod = Console.class.getDeclaredMethod("encoding"); + encodingMethod.setAccessible(true); + consoleCharsetName = (String) encodingMethod.invoke(null); + if (consoleCharsetName == null) { + return Charset.defaultCharset(); + } + } catch (ReflectiveOperationException e) { + Charset defaultCharset = Charset.defaultCharset(); + System.err.println( + "warning: Failed to obtain console character encoding name. Assuming " + + defaultCharset); + return defaultCharset; + } + + try { + return Charset.forName(consoleCharsetName); + } catch (IllegalArgumentException e) { + // On Windows 10, cp65001 is the UTF-8 code page. For some reason, popular JVMs don't + // have a mapping for cp65001... + if ("cp65001".equals(consoleCharsetName)) { + return StandardCharsets.UTF_8; + } + Charset defaultCharset = Charset.defaultCharset(); + System.err.println( + "warning: Console uses unknown character encoding: " + consoleCharsetName + + ". Using " + defaultCharset + " instead"); + return defaultCharset; + } + } + + private static byte[] readEncodedPassword(InputStream in) throws IOException { + ByteArrayOutputStream result = new ByteArrayOutputStream(); + int b; + while ((b = in.read()) != -1) { + if (b == '\n') { + break; + } else if (b == '\r') { + int next = in.read(); + if ((next == -1) || (next == '\n')) { + break; + } + + if (!(in instanceof PushbackInputStream)) { + in = new PushbackInputStream(in); + } + ((PushbackInputStream) in).unread(next); + } + result.write(b); + } + return result.toByteArray(); + } private void assertNotClosed() { if (mClosed) { @@ -124,20 +342,12 @@ class PasswordRetriever implements AutoCloseable { @Override public void close() { - if (mStdIn != null) { - try { - mStdIn.close(); - } catch (IOException ignored) { - } finally { - mStdIn = null; - } - } - for (BufferedReader in : mFileReaders.values()) { + for (InputStream in : mFileInputStreams.values()) { try { in.close(); } catch (IOException ignored) {} } - mFileReaders.clear(); + mFileInputStreams.clear(); mClosed = true; } } diff --git a/src/apksigner/java/com/android/apksigner/help_sign.txt b/src/apksigner/java/com/android/apksigner/help_sign.txt index a673ea9..ad865fe 100644 --- a/src/apksigner/java/com/android/apksigner/help_sign.txt +++ b/src/apksigner/java/com/android/apksigner/help_sign.txt @@ -85,9 +85,7 @@ file in X.509 format (see --key and --cert). signer, KeyStore password is read before the key password is read. ---key-pass Password with which the private key is protected. By - default it is assumed that KeyStore keys are protected - using the same password as their KeyStore (see --ks-pass). +--key-pass Password with which the private key is protected. The following formats are supported: pass:<password> password provided inline env:<name> password provided in the named @@ -96,8 +94,13 @@ file in X.509 format (see --key and --cert). file, as a single line stdin password provided on standard input, as a single line - By default, if the key is password-protected, the tool - will prompt for password via console or standard input. + If --key-pass is not specified for a KeyStore key, this + tool will attempt to load the key using the KeyStore + password and, if that fails, will prompt for key password + and attempt to load the key using that password. + If --key-pass is not specified for a private key file key, + this tool will prompt for key password only if a password + is required. When the same file (including standard input) is used for providing multiple passwords, the passwords are read from the file one line at a time. Passwords are read in the