Your message dated Sat, 11 Mar 2017 11:06:01 +0000
with message-id <[email protected]>
and subject line unblock android-platform-tools-apksig
has caused the Debian Bug report #857407,
regarding unblock android-platform-tools-apksig/0.5+git165~g42d07eb-1
to be marked as done.

This means that you claim that the problem has been dealt with.
If this is not the case it is now your responsibility to reopen the
Bug report if necessary, and/or fix the problem forthwith.

(NB: If you are a system administrator and have no idea what this
message is talking about, this may indicate a serious mail system
misconfiguration somewhere. Please contact [email protected]
immediately.)


-- 
857407: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=857407
Debian Bug Tracking System
Contact [email protected] with problems
--- Begin Message ---
Package: release.debian.org
Severity: normal
User: [email protected]
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 <[email protected]>  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 
<[email protected]>
 Uploaders: Hans-Christoph Steiner <[email protected]>
 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

--- End Message ---
--- Begin Message ---
Unblocked.

--- End Message ---

Reply via email to