diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/KeyPairUtils.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/KeyPairUtils.java index 2f890e62..b7f81898 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/KeyPairUtils.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/KeyPairUtils.java @@ -23,11 +23,14 @@ import java.security.GeneralSecurityException; import java.security.KeyPair; import java.util.Collection; +import org.apache.sshd.common.config.keys.FilePasswordProvider; import org.apache.sshd.common.config.keys.loader.KeyPairResourceLoader; import org.apache.sshd.common.session.SessionContext; import org.apache.sshd.common.util.io.resource.AbstractIoResource; import org.apache.sshd.common.util.security.SecurityUtils; +import org.springframework.util.StringUtils; + final class KeyPairUtils { private static final KeyPairResourceLoader loader = SecurityUtils.getKeyPairResourceParser(); @@ -36,14 +39,18 @@ final class KeyPairUtils { } - static Collection load(SessionContext session, String privateKey) + static Collection load(SessionContext session, String privateKey, String passphrase) throws IOException, GeneralSecurityException { - return loader.loadKeyPairs(session, new StringResource(privateKey), null); + + FilePasswordProvider passwordProvider = StringUtils.hasText(passphrase) ? FilePasswordProvider.of(passphrase) + : FilePasswordProvider.EMPTY; + + return loader.loadKeyPairs(session, new StringResource(privateKey), passwordProvider); } - static boolean isValid(String privateKey) { + static boolean isValid(String privateKey, String passphrase) { try { - return !KeyPairUtils.load(null, privateKey).isEmpty(); + return !KeyPairUtils.load(null, privateKey, passphrase).isEmpty(); } catch (IOException | GeneralSecurityException ignored) { return false; diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/PrivateKeyValidator.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/PrivateKeyValidator.java index c80fefb1..d481180c 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/PrivateKeyValidator.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/ssh/PrivateKeyValidator.java @@ -83,7 +83,7 @@ public class PrivateKeyValidator implements ConstraintValidator keyPairs = KeyPairUtils.load(null, key); + Collection keyPairs = KeyPairUtils.load(null, key, passphrase); return keyPairs.isEmpty() ? null : keyPairs.iterator().next().getPrivate(); } diff --git a/spring-cloud-config-server/src/test/resources/ssh/key-with-passphrase b/spring-cloud-config-server/src/test/resources/ssh/key-with-passphrase new file mode 100644 index 00000000..17fd5ced --- /dev/null +++ b/spring-cloud-config-server/src/test/resources/ssh/key-with-passphrase @@ -0,0 +1,54 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-128-CBC,7310DDCF47A57E8764693DFBACB0FB4A + +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 +-----END RSA PRIVATE KEY-----