You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by tw...@apache.org on 2023/04/01 16:30:07 UTC
[mina-sshd] branch master updated: Utility method FilePasswordProvider.decode()
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:dev@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(