WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit d156b8d

Browse files
committed
fixed openssh key parsing issue
1 parent 340957a commit d156b8d

File tree

2 files changed

+97
-26
lines changed

2 files changed

+97
-26
lines changed

app/server/appsmith-interfaces/src/main/java/com/appsmith/external/helpers/SSHUtils.java

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,24 @@
1313
import net.schmizz.sshj.userauth.keyprovider.OpenSSHKeyFile;
1414
import net.schmizz.sshj.userauth.keyprovider.PKCS8KeyFile;
1515
import net.schmizz.sshj.userauth.method.AuthPublickey;
16+
import org.bouncycastle.asn1.ASN1Primitive;
17+
import org.bouncycastle.asn1.ASN1Sequence;
18+
import org.bouncycastle.asn1.DERNull;
19+
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
20+
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
21+
import org.bouncycastle.asn1.pkcs.RSAPrivateKey;
22+
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
1623
import org.bouncycastle.jce.provider.BouncyCastleProvider;
1724

1825
import java.io.BufferedReader;
19-
import java.io.ByteArrayInputStream;
2026
import java.io.IOException;
21-
import java.io.InputStreamReader;
2227
import java.io.Reader;
2328
import java.io.StringReader;
2429
import java.net.InetSocketAddress;
2530
import java.net.ServerSocket;
2631
import java.nio.charset.StandardCharsets;
2732
import java.security.Security;
33+
import java.util.Base64;
2834

2935
import static com.appsmith.external.constants.ConnectionMethod.CONNECTION_METHOD_SSH;
3036
import static com.appsmith.external.constants.PluginConstants.HostName.LOCALHOST;
@@ -76,7 +82,6 @@ public static SSHTunnelContext createSSHTunnel(
7682
client.addHostKeyVerifier(new PromiscuousVerifier());
7783

7884
client.connect(sshHost, sshPort);
79-
Reader targetReader = new InputStreamReader(new ByteArrayInputStream(key.getDecodedContent()));
8085
String keyContent;
8186
KeyProvider keyFile = null;
8287
try (Reader reader = new StringReader(new String(key.getDecodedContent(), StandardCharsets.UTF_8));
@@ -102,11 +107,17 @@ public static SSHTunnelContext createSSHTunnel(
102107
OpenSSHKeyFile openSSHKeyFile = new OpenSSHKeyFile();
103108
openSSHKeyFile.init(new StringReader(keyContent));
104109
keyFile = openSSHKeyFile;
105-
} else if (keyContent.contains(PKCS_8_PEM_HEADER) || keyContent.contains(PKCS_1_PEM_HEADER)) {
106-
// Handle PEM (PKCS#8) and RSA PEM formats
110+
} else if (keyContent.contains(PKCS_8_PEM_HEADER)) {
111+
// Handle PEM (PKCS#8) format
107112
PKCS8KeyFile pkcs8KeyFile = new PKCS8KeyFile();
108113
pkcs8KeyFile.init(new StringReader(keyContent));
109114
keyFile = pkcs8KeyFile;
115+
} else if (keyContent.contains(PKCS_1_PEM_HEADER)) {
116+
// Handle traditional RSA (PKCS#1) format by converting it to PKCS#8
117+
String pkcs8FormattedKey = convertRsaPkcs1ToPkcs8(keyContent);
118+
PKCS8KeyFile pkcs8KeyFile = new PKCS8KeyFile();
119+
pkcs8KeyFile.init(new StringReader(pkcs8FormattedKey));
120+
keyFile = pkcs8KeyFile;
110121
} else {
111122
throw new AppsmithPluginException(
112123
AppsmithPluginError.PLUGIN_DATASOURCE_ARGUMENT_ERROR, INVALID_SSH_KEY_FORMAT_ERROR_MSG);
@@ -232,4 +243,39 @@ public static Boolean isSSHTunnelConnected(SSHTunnelContext sshTunnelContext) {
232243
SSHClient sshClient = sshTunnelContext.getSshClient();
233244
return sshClient != null && sshClient.isConnected() && sshClient.isAuthenticated();
234245
}
246+
247+
static String convertRsaPkcs1ToPkcs8(String keyContent) throws IOException {
248+
String sanitizedContent = keyContent
249+
.replace("-----BEGIN RSA PRIVATE KEY-----", "")
250+
.replace("-----END RSA PRIVATE KEY-----", "")
251+
.replaceAll("\\s", "");
252+
253+
if (sanitizedContent.isEmpty()) {
254+
throw new IOException("Empty RSA key content");
255+
}
256+
257+
byte[] pkcs1Bytes;
258+
try {
259+
pkcs1Bytes = Base64.getDecoder().decode(sanitizedContent);
260+
} catch (IllegalArgumentException e) {
261+
throw new IOException("Invalid Base64 encoding for RSA key", e);
262+
}
263+
264+
ASN1Primitive asn1Primitive = ASN1Primitive.fromByteArray(pkcs1Bytes);
265+
if (!(asn1Primitive instanceof ASN1Sequence)) {
266+
throw new IOException("Invalid RSA key structure");
267+
}
268+
ASN1Sequence asn1Sequence = (ASN1Sequence) asn1Primitive;
269+
RSAPrivateKey rsaPrivateKey = RSAPrivateKey.getInstance(asn1Sequence);
270+
271+
PrivateKeyInfo privateKeyInfo = new PrivateKeyInfo(
272+
new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE), rsaPrivateKey);
273+
274+
return toPemPrivateKey(privateKeyInfo.getEncoded());
275+
}
276+
277+
private static String toPemPrivateKey(byte[] pkcs8Bytes) {
278+
String base64Encoded = Base64.getMimeEncoder(64, new byte[] {'\n'}).encodeToString(pkcs8Bytes);
279+
return "-----BEGIN PRIVATE KEY-----\n" + base64Encoded + "\n-----END PRIVATE KEY-----\n";
280+
}
235281
}

app/server/appsmith-interfaces/src/test/java/com/appsmith/external/helpers/SSHUtilsTest.java

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,21 @@
66
import com.appsmith.external.models.Property;
77
import com.appsmith.external.models.SSHConnection;
88
import net.schmizz.sshj.SSHClient;
9-
import net.schmizz.sshj.userauth.keyprovider.OpenSSHKeyFile;
109
import net.schmizz.sshj.userauth.keyprovider.PKCS8KeyFile;
10+
import org.bouncycastle.asn1.pkcs.RSAPrivateKey;
1111
import org.bouncycastle.jce.provider.BouncyCastleProvider;
1212
import org.junit.jupiter.api.BeforeAll;
1313
import org.junit.jupiter.api.Test;
1414

15+
import java.io.IOException;
1516
import java.io.Reader;
1617
import java.io.StringReader;
18+
import java.security.KeyPair;
19+
import java.security.KeyPairGenerator;
1720
import java.security.Security;
21+
import java.security.interfaces.RSAPrivateCrtKey;
1822
import java.util.ArrayList;
23+
import java.util.Base64;
1924
import java.util.List;
2025

2126
import static com.appsmith.external.helpers.SSHUtils.getConnectionContext;
@@ -31,28 +36,15 @@ public class SSHUtilsTest {
3136

3237
@BeforeAll
3338
static void setup() {
34-
Security.addProvider(new BouncyCastleProvider()); // Ensure BouncyCastle is available for OpenSSH keys
35-
}
36-
37-
/* Test OpenSSH Key Parsing */
38-
@Test
39-
public void testOpenSSHKeyParsing() throws Exception {
40-
String opensshKey = "-----BEGIN OPENSSH PRIVATE KEY-----\n"
41-
+ "b3BlbnNzaC1rZXktdmVyc2lvbjE=\n"
42-
+ "-----END OPENSSH PRIVATE KEY-----";
43-
44-
Reader reader = new StringReader(opensshKey);
45-
OpenSSHKeyFile openSSHKeyFile = new OpenSSHKeyFile();
46-
openSSHKeyFile.init(reader);
47-
48-
assertNotNull(openSSHKeyFile);
39+
Security.addProvider(
40+
new BouncyCastleProvider()); // Ensure BouncyCastle algorithms are registered for key parsing
4941
}
5042

5143
/* Test PKCS#8 PEM Key Parsing */
5244
@Test
5345
public void testPKCS8PEMKeyParsing() throws Exception {
54-
String pkcs8Key =
55-
"-----BEGIN PRIVATE KEY-----\n" + "MIIEvQIBADANBgkqhkiG9w0BAQEFAASC...\n" + "-----END PRIVATE KEY-----";
46+
KeyPair keyPair = generateRsaKeyPair();
47+
String pkcs8Key = toPkcs8Pem(keyPair);
5648

5749
Reader reader = new StringReader(pkcs8Key);
5850
PKCS8KeyFile pkcs8KeyFile = new PKCS8KeyFile();
@@ -64,14 +56,17 @@ public void testPKCS8PEMKeyParsing() throws Exception {
6456
/* Test RSA PEM Key Parsing */
6557
@Test
6658
public void testRSAPEMKeyParsing() throws Exception {
67-
String rsaKey =
68-
"-----BEGIN RSA PRIVATE KEY-----\n" + "MIIEowIBAAKCAQEA7...\n" + "-----END RSA PRIVATE KEY-----";
59+
KeyPair keyPair = generateRsaKeyPair();
60+
String rsaPkcs1 = toPkcs1Pem((RSAPrivateCrtKey) keyPair.getPrivate());
61+
62+
String convertedKey = SSHUtils.convertRsaPkcs1ToPkcs8(rsaPkcs1);
6963

70-
Reader reader = new StringReader(rsaKey);
64+
Reader reader = new StringReader(convertedKey);
7165
PKCS8KeyFile pkcs8KeyFile = new PKCS8KeyFile();
7266
pkcs8KeyFile.init(reader);
7367

7468
assertNotNull(pkcs8KeyFile);
69+
assertTrue(convertedKey.contains("BEGIN PRIVATE KEY"));
7570
}
7671

7772
/* Test is ssh enabled method */
@@ -168,4 +163,34 @@ public void testDefaultDBPortValue() {
168163

169164
assertEquals(getDBPortFromConfigOrDefault(datasourceConfiguration, 1234L), 1234L);
170165
}
166+
167+
private KeyPair generateRsaKeyPair() throws Exception {
168+
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
169+
generator.initialize(1024);
170+
return generator.generateKeyPair();
171+
}
172+
173+
private String toPkcs8Pem(KeyPair keyPair) {
174+
byte[] pkcs8Bytes = keyPair.getPrivate().getEncoded();
175+
return formatPem("PRIVATE KEY", pkcs8Bytes);
176+
}
177+
178+
private String toPkcs1Pem(RSAPrivateCrtKey privateKey) throws IOException {
179+
RSAPrivateKey bcPrivateKey = new RSAPrivateKey(
180+
privateKey.getModulus(),
181+
privateKey.getPublicExponent(),
182+
privateKey.getPrivateExponent(),
183+
privateKey.getPrimeP(),
184+
privateKey.getPrimeQ(),
185+
privateKey.getPrimeExponentP(),
186+
privateKey.getPrimeExponentQ(),
187+
privateKey.getCrtCoefficient());
188+
189+
return formatPem("RSA PRIVATE KEY", bcPrivateKey.getEncoded());
190+
}
191+
192+
private String formatPem(String header, byte[] encodedBytes) {
193+
String base64 = Base64.getMimeEncoder(64, new byte[] {'\n'}).encodeToString(encodedBytes);
194+
return "-----BEGIN " + header + "-----\n" + base64 + "\n-----END " + header + "-----\n";
195+
}
171196
}

0 commit comments

Comments
 (0)