This is an automated email from the ASF dual-hosted git repository. twolf pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/mina-sshd.git
The following commit(s) were added to refs/heads/master by this push: new 5c8e5eabe Utility method FilePasswordProvider.decode() 5c8e5eabe is described below commit 5c8e5eabe3b25a0bff13d7f272eeeb110c583c64 Author: Thomas Wolf <tw...@apache.org> AuthorDate: Sat Apr 1 18:03:24 2023 +0200 Utility method FilePasswordProvider.decode() Encapsulate the basic decode-with-retries loop in a method. This simplifies code using a FilePasswordProvider, which then only has to deal with actual decoding given a password, and doesn't have to care about the retry logic. --- .../common/config/keys/FilePasswordProvider.java | 58 +++++++++++ .../openssh/OpenSSHKeyPairResourceParser.java | 48 ++------- .../pem/AbstractPEMResourceKeyPairParser.java | 65 +++--------- .../loader/pem/PKCS8PEMResourceKeyPairParser.java | 56 +++-------- .../BouncyCastleKeyPairResourceParser.java | 44 ++------ .../apache/sshd/putty/AbstractPuttyKeyDecoder.java | 112 +++++++-------------- 6 files changed, 140 insertions(+), 243 deletions(-) diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/FilePasswordProvider.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/FilePasswordProvider.java index cab86c72a..3717899a2 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/FilePasswordProvider.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/FilePasswordProvider.java @@ -20,13 +20,17 @@ package org.apache.sshd.common.config.keys; import java.io.IOException; +import java.net.ProtocolException; import java.security.GeneralSecurityException; import java.util.Collections; import java.util.EnumSet; import java.util.Set; +import javax.security.auth.login.FailedLoginException; + import org.apache.sshd.common.NamedResource; import org.apache.sshd.common.session.SessionContext; +import org.apache.sshd.common.util.GenericUtils; /** * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> @@ -87,6 +91,60 @@ public interface FilePasswordProvider { return ResourceDecodeResult.TERMINATE; } + /** + * Something that can produce a result given a password. + * + * @param <T> type of the result + */ + interface Decoder<T> { + T decode(String password) throws IOException, GeneralSecurityException; + } + + /** + * Obtains the password through {@link #getPassword(SessionContext, NamedResource, int)}, invokes the + * {@link Decoder} and then + * {@link #handleDecodeAttemptResult(SessionContext, NamedResource, int, String, Exception)} and then returns the + * decoded result. If the decoder fails and the {@link ResourceDecodeResult} is {@link ResourceDecodeResult#RETRY}, + * the whole process is re-tried. + * + * @param <T> Result type of the decoder + * @param session {@link SessionContext}, may be {@code null} + * @param resourceKey {@link NamedResource} used for error reporting + * @param decoder {@link Decoder} to produce the result given a password + * @return the decoder's result, or {@code null} if none + * @throws IOException if an I/O problem occurs + * @throws GeneralSecurityException if the decoder throws it + */ + default <T> T decode(SessionContext session, NamedResource resourceKey, Decoder<? extends T> decoder) + throws IOException, GeneralSecurityException { + for (int retryCount = 0;; retryCount++) { + String pwd = getPassword(session, resourceKey, retryCount); + if (GenericUtils.isEmpty(pwd)) { + throw new FailedLoginException("No password data for encrypted resource=" + resourceKey); + } + try { + T result = decoder.decode(pwd); + handleDecodeAttemptResult(session, resourceKey, retryCount, pwd, null); + return result; + } catch (IOException | GeneralSecurityException | RuntimeException e) { + ResourceDecodeResult result = handleDecodeAttemptResult(session, resourceKey, retryCount, pwd, e); + if (result == null) { + throw e; + } + switch (result) { + case TERMINATE: + throw e; + case RETRY: + continue; + case IGNORE: + return null; + default: + throw new ProtocolException("Unsupported decode attempt result (" + result + ") for " + resourceKey); + } + } + } + } + static FilePasswordProvider of(String password) { return (session, resource, index) -> password; } diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParser.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParser.java index 9e81001c8..09e8d61bc 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParser.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/openssh/OpenSSHKeyPairResourceParser.java @@ -23,7 +23,6 @@ import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.StreamCorruptedException; -import java.net.ProtocolException; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; import java.security.InvalidKeyException; @@ -49,7 +48,6 @@ import org.apache.sshd.common.NamedResource; import org.apache.sshd.common.cipher.BuiltinCiphers; import org.apache.sshd.common.cipher.CipherFactory; import org.apache.sshd.common.config.keys.FilePasswordProvider; -import org.apache.sshd.common.config.keys.FilePasswordProvider.ResourceDecodeResult; import org.apache.sshd.common.config.keys.KeyEntryResolver; import org.apache.sshd.common.config.keys.KeyUtils; import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder; @@ -171,45 +169,15 @@ public class OpenSSHKeyPairResourceParser extends AbstractKeyPairResourceParser } else { encryptedData = KeyEntryResolver.readRLEBytes(stream, MAX_PRIVATE_KEY_DATA_SIZE); } - for (int retryCount = 0;; retryCount++) { - String pwd = passwordProvider.getPassword(session, resourceKey, retryCount); - if (GenericUtils.isEmpty(pwd)) { - return Collections.emptyList(); - } - - List<KeyPair> keys; - try { - byte[] decryptedData = kdfOptions.decodePrivateKeyBytes(session, resourceKey, cipherSpec, - encryptedData, pwd); - try (InputStream bais = new ByteArrayInputStream(decryptedData)) { - keys = readPrivateKeys(session, resourceKey, context, publicKeys, passwordProvider, bais); - } finally { - Arrays.fill(decryptedData, (byte) 0); // get rid of sensitive data a.s.a.p. - } - } catch (IOException | GeneralSecurityException | RuntimeException e) { - ResourceDecodeResult result = passwordProvider.handleDecodeAttemptResult(session, resourceKey, retryCount, pwd, - e); - pwd = null; // get rid of sensitive data a.s.a.p. - if (result == null) { - result = ResourceDecodeResult.TERMINATE; - } - - switch (result) { - case TERMINATE: - throw e; - case RETRY: - continue; - case IGNORE: - return Collections.emptyList(); - default: - throw new ProtocolException("Unsupported decode attempt result (" + result + ") for " + resourceKey); - } + Collection<KeyPair> keys = passwordProvider.decode(session, resourceKey, pwd -> { + byte[] decryptedData = kdfOptions.decodePrivateKeyBytes(session, resourceKey, cipherSpec, encryptedData, pwd); + try (InputStream bais = new ByteArrayInputStream(decryptedData)) { + return readPrivateKeys(session, resourceKey, context, publicKeys, passwordProvider, bais); + } finally { + Arrays.fill(decryptedData, (byte) 0); } - - passwordProvider.handleDecodeAttemptResult(session, resourceKey, retryCount, pwd, null); - pwd = null; // get rid of sensitive data a.s.a.p. - return keys; - } + }); + return keys == null ? Collections.emptyList() : keys; } protected OpenSSHKdfOptions resolveKdfOptions( diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/AbstractPEMResourceKeyPairParser.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/AbstractPEMResourceKeyPairParser.java index 6588e85ed..56ad7e3c7 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/AbstractPEMResourceKeyPairParser.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/AbstractPEMResourceKeyPairParser.java @@ -22,7 +22,6 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.StreamCorruptedException; -import java.net.ProtocolException; import java.security.GeneralSecurityException; import java.security.KeyPair; import java.security.NoSuchAlgorithmException; @@ -34,11 +33,9 @@ import java.util.Map; import java.util.TreeMap; import javax.security.auth.login.CredentialException; -import javax.security.auth.login.FailedLoginException; import org.apache.sshd.common.NamedResource; import org.apache.sshd.common.config.keys.FilePasswordProvider; -import org.apache.sshd.common.config.keys.FilePasswordProvider.ResourceDecodeResult; import org.apache.sshd.common.config.keys.loader.AbstractKeyPairResourceParser; import org.apache.sshd.common.config.keys.loader.KeyPairResourceParser; import org.apache.sshd.common.config.keys.loader.PrivateKeyEncryptionContext; @@ -150,56 +147,24 @@ public abstract class AbstractPEMResourceKeyPairParser throw new CredentialException("Missing password provider for encrypted resource=" + resourceKey); } - for (int retryIndex = 0;; retryIndex++) { - String password = passwordProvider.getPassword(session, resourceKey, retryIndex); - Collection<KeyPair> keys; + byte[] encryptedData = KeyPairResourceParser.extractDataBytes(dataLines); + String algorithm = algInfo; + byte[] iv = initVector; + Collection<KeyPair> keys = passwordProvider.decode(session, resourceKey, password -> { + PrivateKeyEncryptionContext encContext = new PrivateKeyEncryptionContext(algorithm); + encContext.setPassword(password); + encContext.setInitVector(iv); + byte[] decodedData = GenericUtils.EMPTY_BYTE_ARRAY; try { - if (GenericUtils.isEmpty(password)) { - throw new FailedLoginException("No password data for encrypted resource=" + resourceKey); - } - - PrivateKeyEncryptionContext encContext = new PrivateKeyEncryptionContext(algInfo); - encContext.setPassword(password); - encContext.setInitVector(initVector); - - byte[] encryptedData = GenericUtils.EMPTY_BYTE_ARRAY; - byte[] decodedData = GenericUtils.EMPTY_BYTE_ARRAY; - try { - encryptedData = KeyPairResourceParser.extractDataBytes(dataLines); - decodedData = applyPrivateKeyCipher(encryptedData, encContext, false); - try (InputStream bais = new ByteArrayInputStream(decodedData)) { - keys = extractKeyPairs(session, resourceKey, beginMarker, endMarker, passwordProvider, bais, - headers); - } - } finally { - Arrays.fill(encryptedData, (byte) 0); // get rid of sensitive data a.s.a.p. - Arrays.fill(decodedData, (byte) 0); // get rid of sensitive data a.s.a.p. - } - } catch (IOException | GeneralSecurityException | RuntimeException e) { - ResourceDecodeResult result - = passwordProvider.handleDecodeAttemptResult(session, resourceKey, retryIndex, password, e); - password = null; // get rid of sensitive data a.s.a.p. - if (result == null) { - result = ResourceDecodeResult.TERMINATE; - } - - switch (result) { - case TERMINATE: - throw e; - case RETRY: - continue; - case IGNORE: - return Collections.emptyList(); - default: - throw new ProtocolException( - "Unsupported decode attempt result (" + result + ") for " + resourceKey); + decodedData = applyPrivateKeyCipher(encryptedData, encContext, false); + try (InputStream bais = new ByteArrayInputStream(decodedData)) { + return extractKeyPairs(session, resourceKey, beginMarker, endMarker, passwordProvider, bais, headers); } + } finally { + Arrays.fill(decodedData, (byte) 0); } - - passwordProvider.handleDecodeAttemptResult(session, resourceKey, retryIndex, password, null); - password = null; // get rid of sensitive data a.s.a.p. - return keys; - } + }); + return keys == null ? Collections.emptyList() : keys; } return super.extractKeyPairs(session, resourceKey, beginMarker, endMarker, passwordProvider, dataLines, headers); diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParser.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParser.java index df8f6d3b2..fbb5d233f 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParser.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/pem/PKCS8PEMResourceKeyPairParser.java @@ -21,7 +21,6 @@ package org.apache.sshd.common.config.keys.loader.pem; import java.io.IOException; import java.io.InputStream; -import java.net.ProtocolException; import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.KeyPair; @@ -36,12 +35,10 @@ import java.util.List; import java.util.Map; import javax.security.auth.login.CredentialException; -import javax.security.auth.login.FailedLoginException; import org.apache.sshd.common.NamedResource; import org.apache.sshd.common.cipher.ECCurves; import org.apache.sshd.common.config.keys.FilePasswordProvider; -import org.apache.sshd.common.config.keys.FilePasswordProvider.ResourceDecodeResult; import org.apache.sshd.common.config.keys.KeyUtils; import org.apache.sshd.common.session.SessionContext; import org.apache.sshd.common.util.GenericUtils; @@ -83,13 +80,18 @@ public class PKCS8PEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar FilePasswordProvider passwordProvider, InputStream stream, Map<String, String> headers) throws IOException, GeneralSecurityException { - byte[] encBytes = IoUtils.toByteArray(stream); + byte[] privateBytes = IoUtils.toByteArray(stream); if (beginMarker.contains(BEGIN_ENCRYPTED_MARKER)) { // RFC 5958 EncryptedPrivateKeyInfo. - return decryptKeyPairs(session, resourceKey, passwordProvider, encBytes); + return decryptKeyPairs(session, resourceKey, passwordProvider, privateBytes); + } + PKCS8PrivateKeyInfo pkcs8Info = new PKCS8PrivateKeyInfo(privateBytes); + try { + return extractKeyPairs(privateBytes, pkcs8Info); + } finally { + pkcs8Info.clear(); + Arrays.fill(privateBytes, (byte) 0); } - PKCS8PrivateKeyInfo pkcs8Info = new PKCS8PrivateKeyInfo(encBytes); - return extractKeyPairs(encBytes, pkcs8Info); } public Collection<KeyPair> decryptKeyPairs( @@ -104,48 +106,22 @@ public class PKCS8PEMResourceKeyPairParser extends AbstractPEMResourceKeyPairPar // See https://stackoverflow.com/questions/66286457/load-an-encrypted-pcks8-pem-private-key-in-java Decryptor decryptor = SecurityUtils.getBouncycastleEncryptedPrivateKeyInfoDecryptor(); - Collection<KeyPair> keyPairs = Collections.emptyList(); - for (int retryIndex = 0;; retryIndex++) { - String password = passwordProvider.getPassword(session, resourceKey, retryIndex); + Collection<KeyPair> keyPairs = passwordProvider.decode(session, resourceKey, password -> { + char[] key = password.toCharArray(); try { - char[] key = password != null ? password.toCharArray() : null; - if (GenericUtils.isEmpty(key)) { - throw new FailedLoginException("No password data for encrypted resource=" + resourceKey); - } byte[] decrypted = decryptor.decrypt(encrypted, key); PKCS8PrivateKeyInfo pkcs8Info = new PKCS8PrivateKeyInfo(decrypted); try { - keyPairs = extractKeyPairs(decrypted, pkcs8Info); + return extractKeyPairs(decrypted, pkcs8Info); } finally { pkcs8Info.clear(); - Arrays.fill(key, (char) 0); - if (decrypted != null) { - Arrays.fill(decrypted, (byte) 0); - } - } - passwordProvider.handleDecodeAttemptResult(session, resourceKey, retryIndex, password, null); - break; - } catch (IOException | GeneralSecurityException | RuntimeException e) { - ResourceDecodeResult result = passwordProvider.handleDecodeAttemptResult(session, resourceKey, retryIndex, - password, e); - if (result == null) { - result = ResourceDecodeResult.TERMINATE; - } - switch (result) { - case TERMINATE: - throw e; - case RETRY: - continue; - case IGNORE: - return null; - default: - throw new ProtocolException("Unsupported decode attempt result (" + result + ") for " + resourceKey); + Arrays.fill(decrypted, (byte) 0); } } finally { - password = null; + Arrays.fill(key, (char) 0); } - } - return keyPairs; + }); + return keyPairs == null ? Collections.emptyList() : keyPairs; } public Collection<KeyPair> extractKeyPairs(byte[] encBytes, PKCS8PrivateKeyInfo pkcs8Info) diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleKeyPairResourceParser.java b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleKeyPairResourceParser.java index 14a5d4ea2..fb138987e 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleKeyPairResourceParser.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/security/bouncycastle/BouncyCastleKeyPairResourceParser.java @@ -23,7 +23,6 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.net.ProtocolException; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; import java.security.KeyPair; @@ -35,14 +34,11 @@ import java.util.List; import java.util.Map; import javax.security.auth.login.CredentialException; -import javax.security.auth.login.FailedLoginException; import org.apache.sshd.common.NamedResource; import org.apache.sshd.common.config.keys.FilePasswordProvider; -import org.apache.sshd.common.config.keys.FilePasswordProvider.ResourceDecodeResult; import org.apache.sshd.common.config.keys.loader.AbstractKeyPairResourceParser; import org.apache.sshd.common.session.SessionContext; -import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.io.IoUtils; import org.apache.sshd.common.util.security.SecurityProviderRegistrar; import org.apache.sshd.common.util.security.SecurityUtils; @@ -128,40 +124,12 @@ public class BouncyCastleKeyPairResourceParser extends AbstractKeyPairResourcePa throw new CredentialException("Missing password provider for encrypted resource=" + resourceKey); } - for (int retryIndex = 0;; retryIndex++) { - String password = provider.getPassword(session, resourceKey, retryIndex); - PEMKeyPair decoded; - try { - if (GenericUtils.isEmpty(password)) { - throw new FailedLoginException("No password data for encrypted resource=" + resourceKey); - } - - JcePEMDecryptorProviderBuilder decryptorBuilder = new JcePEMDecryptorProviderBuilder(); - PEMDecryptorProvider pemDecryptor = decryptorBuilder.build(password.toCharArray()); - decoded = ((PEMEncryptedKeyPair) o).decryptKeyPair(pemDecryptor); - } catch (IOException | GeneralSecurityException | RuntimeException e) { - ResourceDecodeResult result - = provider.handleDecodeAttemptResult(session, resourceKey, retryIndex, password, e); - if (result == null) { - result = ResourceDecodeResult.TERMINATE; - } - switch (result) { - case TERMINATE: - throw e; - case RETRY: - continue; - case IGNORE: - return null; - default: - throw new ProtocolException( - "Unsupported decode attempt result (" + result + ") for " + resourceKey); - } - } - - o = decoded; - provider.handleDecodeAttemptResult(session, resourceKey, retryIndex, password, null); - break; - } + PEMEncryptedKeyPair encrypted = (PEMEncryptedKeyPair) o; + o = provider.decode(session, resourceKey, password -> { + JcePEMDecryptorProviderBuilder decryptorBuilder = new JcePEMDecryptorProviderBuilder(); + PEMDecryptorProvider pemDecryptor = decryptorBuilder.build(password.toCharArray()); + return encrypted.decryptKeyPair(pemDecryptor); + }); } if (o instanceof PEMKeyPair) { diff --git a/sshd-putty/src/main/java/org/apache/sshd/putty/AbstractPuttyKeyDecoder.java b/sshd-putty/src/main/java/org/apache/sshd/putty/AbstractPuttyKeyDecoder.java index 263a164be..9bc7106d6 100644 --- a/sshd-putty/src/main/java/org/apache/sshd/putty/AbstractPuttyKeyDecoder.java +++ b/sshd-putty/src/main/java/org/apache/sshd/putty/AbstractPuttyKeyDecoder.java @@ -23,7 +23,6 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.StreamCorruptedException; -import java.net.ProtocolException; import java.security.GeneralSecurityException; import java.security.KeyPair; import java.security.PrivateKey; @@ -38,11 +37,8 @@ import java.util.List; import java.util.Map; import java.util.TreeMap; -import javax.security.auth.login.FailedLoginException; - import org.apache.sshd.common.NamedResource; import org.apache.sshd.common.config.keys.FilePasswordProvider; -import org.apache.sshd.common.config.keys.FilePasswordProvider.ResourceDecodeResult; import org.apache.sshd.common.config.keys.impl.AbstractIdentityResourceLoader; import org.apache.sshd.common.config.keys.loader.KeyPairResourceParser; import org.apache.sshd.common.session.SessionContext; @@ -180,85 +176,51 @@ public abstract class AbstractPuttyKeyDecoder<PUB extends PublicKey, PRV extends String pubData, String prvData, String prvEncryption, FilePasswordProvider passwordProvider, Map<String, String> headers) throws IOException, GeneralSecurityException { - byte[] pubBytes = GenericUtils.EMPTY_BYTE_ARRAY; - byte[] prvBytes = GenericUtils.EMPTY_BYTE_ARRAY; - try { - Decoder b64Decoder = Base64.getDecoder(); - pubBytes = b64Decoder.decode(pubData); - prvBytes = b64Decoder.decode(prvData); - if (GenericUtils.isEmpty(prvEncryption) - || NO_PRIVATE_KEY_ENCRYPTION_VALUE.equalsIgnoreCase(prvEncryption)) { + Decoder b64Decoder = Base64.getDecoder(); + byte[] pubBytes = b64Decoder.decode(pubData); + byte[] prvBytes = b64Decoder.decode(prvData); + if (GenericUtils.isEmpty(prvEncryption) || NO_PRIVATE_KEY_ENCRYPTION_VALUE.equalsIgnoreCase(prvEncryption)) { + try { return loadKeyPairs(resourceKey, formatVersion, pubBytes, prvBytes, headers); + } finally { + Arrays.fill(prvBytes, (byte) 0); } + } - // format is "<cipher><bits>-<mode>" - e.g., "aes256-cbc" - int pos = prvEncryption.indexOf('-'); - if (pos <= 0) { - throw new StreamCorruptedException("Missing private key encryption mode in " + prvEncryption); - } - - String mode = prvEncryption.substring(pos + 1).toUpperCase(); - String algName = null; - int numBits = 0; - for (int index = 0; index < pos; index++) { - char ch = prvEncryption.charAt(index); - if ((ch >= '0') && (ch <= '9')) { - algName = prvEncryption.substring(0, index).toUpperCase(); - numBits = Integer.parseInt(prvEncryption.substring(index, pos)); - break; - } - } + // format is "<cipher><bits>-<mode>" - e.g., "aes256-cbc" + int pos = prvEncryption.indexOf('-'); + if (pos <= 0) { + throw new StreamCorruptedException("Missing private key encryption mode in " + prvEncryption); + } - if (GenericUtils.isEmpty(algName) || (numBits <= 0)) { - throw new StreamCorruptedException("Missing private key encryption algorithm details in " + prvEncryption); + String mode = prvEncryption.substring(pos + 1).toUpperCase(); + String algName = null; + int numBits = 0; + for (int index = 0; index < pos; index++) { + char ch = prvEncryption.charAt(index); + if ((ch >= '0') && (ch <= '9')) { + algName = prvEncryption.substring(0, index).toUpperCase(); + numBits = Integer.parseInt(prvEncryption.substring(index, pos)); + break; } + } - for (int retryIndex = 0;; retryIndex++) { - String password = passwordProvider.getPassword(session, resourceKey, retryIndex); - - Collection<KeyPair> keys; - try { - if (GenericUtils.isEmpty(password)) { - throw new FailedLoginException("No password data for encrypted resource=" + resourceKey); - } - - byte[] decBytes = PuttyKeyPairResourceParser.decodePrivateKeyBytes( - formatVersion, prvBytes, algName, numBits, mode, password, headers); - try { - keys = loadKeyPairs(resourceKey, formatVersion, pubBytes, decBytes, headers); - } finally { - Arrays.fill(decBytes, (byte) 0); // eliminate sensitive data a.s.a.p. - } - } catch (IOException | GeneralSecurityException | RuntimeException e) { - ResourceDecodeResult result - = passwordProvider.handleDecodeAttemptResult(session, resourceKey, retryIndex, password, e); - password = null; // get rid of sensitive data a.s.a.p. - if (result == null) { - result = ResourceDecodeResult.TERMINATE; - } - - password = null; // GC hint - don't keep sensitive data in memory longer than necessary - switch (result) { - case TERMINATE: - throw e; - case RETRY: - continue; - case IGNORE: - return Collections.emptyList(); - default: - throw new ProtocolException( - "Unsupported decode attempt result (" + result + ") for " + resourceKey); - } - } + if (GenericUtils.isEmpty(algName) || (numBits <= 0)) { + throw new StreamCorruptedException("Missing private key encryption algorithm details in " + prvEncryption); + } - passwordProvider.handleDecodeAttemptResult(session, resourceKey, retryIndex, password, null); - password = null; // get rid of sensitive data a.s.a.p. - return keys; + String algorithm = algName; + int bits = numBits; + Collection<KeyPair> keys = passwordProvider.decode(session, resourceKey, password -> { + byte[] decBytes = PuttyKeyPairResourceParser.decodePrivateKeyBytes(formatVersion, prvBytes, algorithm, bits, mode, + password, headers); + try { + return loadKeyPairs(resourceKey, formatVersion, pubBytes, decBytes, headers); + } finally { + Arrays.fill(decBytes, (byte) 0); // eliminate sensitive data a.s.a.p. } - } finally { - Arrays.fill(pubBytes, (byte) 0); // eliminate sensitive data a.s.a.p. - Arrays.fill(prvBytes, (byte) 0); // eliminate sensitive data a.s.a.p. - } + }); + return keys == null ? Collections.emptyList() : keys; } public Collection<KeyPair> loadKeyPairs(