mirror of
https://github.com/hierynomus/sshj.git
synced 2025-12-06 15:20:54 +03:00
Improved malformed file handling for OpenSSH Private Keys (#898)
This commit is contained in:
@@ -98,19 +98,19 @@ public class OpenSSHKeyV1KeyFile extends BaseFileKeyProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected KeyPair readKeyPair() throws IOException {
|
protected KeyPair readKeyPair() throws IOException {
|
||||||
BufferedReader reader = new BufferedReader(resource.getReader());
|
final BufferedReader reader = new BufferedReader(resource.getReader());
|
||||||
try {
|
try {
|
||||||
if (!checkHeader(reader)) {
|
if (checkHeader(reader)) {
|
||||||
throw new IOException("This key is not in 'openssh-key-v1' format");
|
final String encodedPrivateKey = readEncodedKey(reader);
|
||||||
|
byte[] decodedPrivateKey = Base64.getDecoder().decode(encodedPrivateKey);
|
||||||
|
final PlainBuffer bufferedPrivateKey = new PlainBuffer(decodedPrivateKey);
|
||||||
|
return readDecodedKeyPair(bufferedPrivateKey);
|
||||||
|
} else {
|
||||||
|
final String message = String.format("File header not found [%s%s]", BEGIN, OPENSSH_PRIVATE_KEY);
|
||||||
|
throw new IOException(message);
|
||||||
}
|
}
|
||||||
|
} catch (final GeneralSecurityException e) {
|
||||||
String keyFile = readKeyFile(reader);
|
throw new SSHRuntimeException("Read OpenSSH Version 1 Key failed", e);
|
||||||
byte[] decode = Base64.getDecoder().decode(keyFile);
|
|
||||||
PlainBuffer keyBuffer = new PlainBuffer(decode);
|
|
||||||
return readDecodedKeyPair(keyBuffer);
|
|
||||||
|
|
||||||
} catch (GeneralSecurityException e) {
|
|
||||||
throw new SSHRuntimeException(e);
|
|
||||||
} finally {
|
} finally {
|
||||||
IOUtils.closeQuietly(reader);
|
IOUtils.closeQuietly(reader);
|
||||||
}
|
}
|
||||||
@@ -149,7 +149,7 @@ public class OpenSSHKeyV1KeyFile extends BaseFileKeyProvider {
|
|||||||
logger.debug("Reading unencrypted keypair");
|
logger.debug("Reading unencrypted keypair");
|
||||||
return readUnencrypted(privateKeyBuffer, publicKey);
|
return readUnencrypted(privateKeyBuffer, publicKey);
|
||||||
} else {
|
} else {
|
||||||
logger.info("Keypair is encrypted with: " + cipherName + ", " + kdfName + ", " + Arrays.toString(kdfOptions));
|
logger.info("Keypair is encrypted with: {}, {}, {}", cipherName, kdfName, Arrays.toString(kdfOptions));
|
||||||
while (true) {
|
while (true) {
|
||||||
PlainBuffer decryptionBuffer = new PlainBuffer(privateKeyBuffer);
|
PlainBuffer decryptionBuffer = new PlainBuffer(privateKeyBuffer);
|
||||||
PlainBuffer decrypted = decryptBuffer(decryptionBuffer, cipherName, kdfName, kdfOptions);
|
PlainBuffer decrypted = decryptBuffer(decryptionBuffer, cipherName, kdfName, kdfOptions);
|
||||||
@@ -209,14 +209,26 @@ public class OpenSSHKeyV1KeyFile extends BaseFileKeyProvider {
|
|||||||
return KeyType.fromString(plainBuffer.readString()).readPubKeyFromBuffer(plainBuffer);
|
return KeyType.fromString(plainBuffer.readString()).readPubKeyFromBuffer(plainBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String readKeyFile(final BufferedReader reader) throws IOException {
|
private String readEncodedKey(final BufferedReader reader) throws IOException {
|
||||||
StringBuilder sb = new StringBuilder();
|
final StringBuilder builder = new StringBuilder();
|
||||||
|
|
||||||
|
boolean footerFound = false;
|
||||||
String line = reader.readLine();
|
String line = reader.readLine();
|
||||||
while (!line.startsWith(END)) {
|
while (line != null) {
|
||||||
sb.append(line);
|
if (line.startsWith(END)) {
|
||||||
|
footerFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
builder.append(line);
|
||||||
line = reader.readLine();
|
line = reader.readLine();
|
||||||
}
|
}
|
||||||
return sb.toString();
|
|
||||||
|
if (footerFound) {
|
||||||
|
return builder.toString();
|
||||||
|
} else {
|
||||||
|
final String message = String.format("File footer not found [%s%s]", END, OPENSSH_PRIVATE_KEY);
|
||||||
|
throw new IOException(message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkHeader(final BufferedReader reader) throws IOException {
|
private boolean checkHeader(final BufferedReader reader) throws IOException {
|
||||||
@@ -244,7 +256,7 @@ public class OpenSSHKeyV1KeyFile extends BaseFileKeyProvider {
|
|||||||
// The private key section contains both the public key and the private key
|
// The private key section contains both the public key and the private key
|
||||||
String keyType = keyBuffer.readString(); // string keytype
|
String keyType = keyBuffer.readString(); // string keytype
|
||||||
KeyType kt = KeyType.fromString(keyType);
|
KeyType kt = KeyType.fromString(keyType);
|
||||||
logger.info("Read key type: {}", keyType, kt);
|
|
||||||
KeyPair kp;
|
KeyPair kp;
|
||||||
switch (kt) {
|
switch (kt) {
|
||||||
case ED25519:
|
case ED25519:
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import net.schmizz.sshj.userauth.password.PasswordFinder;
|
|||||||
import net.schmizz.sshj.userauth.password.PasswordUtils;
|
import net.schmizz.sshj.userauth.password.PasswordUtils;
|
||||||
import net.schmizz.sshj.userauth.password.Resource;
|
import net.schmizz.sshj.userauth.password.Resource;
|
||||||
import net.schmizz.sshj.util.KeyUtil;
|
import net.schmizz.sshj.util.KeyUtil;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.io.TempDir;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
@@ -226,6 +225,22 @@ public class OpenSSHKeyFileTest {
|
|||||||
assertThat(aPrivate.getAlgorithm(), equalTo("EdDSA"));
|
assertThat(aPrivate.getAlgorithm(), equalTo("EdDSA"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldHandlePrivateKeyMissingHeader() {
|
||||||
|
OpenSSHKeyV1KeyFile keyFile = new OpenSSHKeyV1KeyFile();
|
||||||
|
keyFile.init(new File("src/test/resources/keyformats/pkcs8"));
|
||||||
|
final IOException exception = assertThrows(IOException.class, keyFile::getPrivate);
|
||||||
|
assertTrue(exception.getMessage().contains("header not found"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldHandlePrivateKeyMissingFooter() {
|
||||||
|
final OpenSSHKeyV1KeyFile keyFile = new OpenSSHKeyV1KeyFile();
|
||||||
|
keyFile.init(new File("src/test/resources/keytypes/test_ed25519_missing_footer"));
|
||||||
|
final IOException exception = assertThrows(IOException.class, keyFile::getPrivate);
|
||||||
|
assertTrue(exception.getMessage().contains("footer not found"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldLoadProtectedED25519PrivateKeyAes256CTR() throws IOException {
|
public void shouldLoadProtectedED25519PrivateKeyAes256CTR() throws IOException {
|
||||||
checkOpenSSHKeyV1("src/test/resources/keytypes/ed25519_protected", "sshjtest", false);
|
checkOpenSSHKeyV1("src/test/resources/keytypes/ed25519_protected", "sshjtest", false);
|
||||||
@@ -245,7 +260,7 @@ public class OpenSSHKeyFileTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldFailOnIncorrectPassphraseAfterRetries() throws IOException {
|
public void shouldFailOnIncorrectPassphraseAfterRetries() {
|
||||||
assertThrows(KeyDecryptionFailedException.class, () -> {
|
assertThrows(KeyDecryptionFailedException.class, () -> {
|
||||||
OpenSSHKeyV1KeyFile keyFile = new OpenSSHKeyV1KeyFile();
|
OpenSSHKeyV1KeyFile keyFile = new OpenSSHKeyV1KeyFile();
|
||||||
keyFile.init(new File("src/test/resources/keytypes/ed25519_aes256cbc.pem"), new PasswordFinder() {
|
keyFile.init(new File("src/test/resources/keytypes/ed25519_aes256cbc.pem"), new PasswordFinder() {
|
||||||
|
|||||||
6
src/test/resources/keytypes/test_ed25519_missing_footer
Normal file
6
src/test/resources/keytypes/test_ed25519_missing_footer
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||||
|
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||||
|
QyNTUxOQAAACAwHSYkZJATPMgvLHkxKAJ9j38Gyyq5HGoWdMcT6FiAiQAAAJDimgR84poE
|
||||||
|
fAAAAAtzc2gtZWQyNTUxOQAAACAwHSYkZJATPMgvLHkxKAJ9j38Gyyq5HGoWdMcT6FiAiQ
|
||||||
|
AAAECmsckQycWnfGQK6XtQpaMGODbAkMQOdJNK6XJSipB7dDAdJiRkkBM8yC8seTEoAn2P
|
||||||
|
fwbLKrkcahZ0xxPoWICJAAAACXJvb3RAc3NoagECAwQ=
|
||||||
Reference in New Issue
Block a user