mirror of
https://github.com/hierynomus/sshj.git
synced 2025-12-06 07:10:53 +03:00
full support for encrypted PuTTY v3 files (#730)
* full support for encrypted PuTTY v3 files (Argon2 library not included) * simplified the PuTTYKeyDerivation interface and provided an abstract PuTTYArgon2 class for an easy Argon2 integration * use Argon2 implementation from Bouncy Castle * missing license header added * license header again * unit tests extended to cover all Argon2 variants and non-standard Argon2 parameters; verify the loaded keys
This commit is contained in:
@@ -29,6 +29,8 @@ import net.schmizz.sshj.common.SecurityUtils;
|
|||||||
import net.schmizz.sshj.userauth.password.PasswordUtils;
|
import net.schmizz.sshj.userauth.password.PasswordUtils;
|
||||||
import org.bouncycastle.asn1.nist.NISTNamedCurves;
|
import org.bouncycastle.asn1.nist.NISTNamedCurves;
|
||||||
import org.bouncycastle.asn1.x9.X9ECParameters;
|
import org.bouncycastle.asn1.x9.X9ECParameters;
|
||||||
|
import org.bouncycastle.crypto.generators.Argon2BytesGenerator;
|
||||||
|
import org.bouncycastle.crypto.params.Argon2Parameters;
|
||||||
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
|
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
|
||||||
import org.bouncycastle.util.encoders.Hex;
|
import org.bouncycastle.util.encoders.Hex;
|
||||||
|
|
||||||
@@ -40,7 +42,10 @@ import java.io.*;
|
|||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.security.*;
|
import java.security.*;
|
||||||
import java.security.spec.*;
|
import java.security.spec.*;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -84,20 +89,18 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Integer keyFileVersion;
|
||||||
private byte[] privateKey;
|
private byte[] privateKey;
|
||||||
private byte[] publicKey;
|
private byte[] publicKey;
|
||||||
|
private byte[] verifyHmac; // only used by v3 keys
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Key type
|
* Key type
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public KeyType getType() throws IOException {
|
public KeyType getType() throws IOException {
|
||||||
for (String h : headers.keySet()) {
|
String headerName = String.format("PuTTY-User-Key-File-%d", this.keyFileVersion);
|
||||||
if (h.startsWith("PuTTY-User-Key-File-")) {
|
return KeyType.fromString(headers.get(headerName));
|
||||||
return KeyType.fromString(headers.get(h));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return KeyType.UNKNOWN;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isEncrypted() throws IOException {
|
public boolean isEncrypted() throws IOException {
|
||||||
@@ -207,6 +210,7 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void parseKeyPair() throws IOException {
|
protected void parseKeyPair() throws IOException {
|
||||||
|
this.keyFileVersion = null;
|
||||||
BufferedReader r = new BufferedReader(resource.getReader());
|
BufferedReader r = new BufferedReader(resource.getReader());
|
||||||
// Parse the text into headers and payloads
|
// Parse the text into headers and payloads
|
||||||
try {
|
try {
|
||||||
@@ -217,6 +221,9 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
|
|||||||
if (idx > 0) {
|
if (idx > 0) {
|
||||||
headerName = line.substring(0, idx);
|
headerName = line.substring(0, idx);
|
||||||
headers.put(headerName, line.substring(idx + 2));
|
headers.put(headerName, line.substring(idx + 2));
|
||||||
|
if (headerName.startsWith("PuTTY-User-Key-File-")) {
|
||||||
|
this.keyFileVersion = Integer.parseInt(headerName.substring(20));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
String s = payload.get(headerName);
|
String s = payload.get(headerName);
|
||||||
if (s == null) {
|
if (s == null) {
|
||||||
@@ -232,6 +239,9 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
|
|||||||
} finally {
|
} finally {
|
||||||
r.close();
|
r.close();
|
||||||
}
|
}
|
||||||
|
if (this.keyFileVersion == null) {
|
||||||
|
throw new IOException("Invalid key file format: missing \"PuTTY-User-Key-File-?\" entry");
|
||||||
|
}
|
||||||
// Retrieve keys from payload
|
// Retrieve keys from payload
|
||||||
publicKey = Base64.decode(payload.get("Public-Lines"));
|
publicKey = Base64.decode(payload.get("Public-Lines"));
|
||||||
if (this.isEncrypted()) {
|
if (this.isEncrypted()) {
|
||||||
@@ -242,8 +252,14 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
|
|||||||
passphrase = "".toCharArray();
|
passphrase = "".toCharArray();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
privateKey = this.decrypt(Base64.decode(payload.get("Private-Lines")), new String(passphrase));
|
privateKey = this.decrypt(Base64.decode(payload.get("Private-Lines")), passphrase);
|
||||||
this.verify(new String(passphrase));
|
Mac mac;
|
||||||
|
if (this.keyFileVersion <= 2) {
|
||||||
|
mac = this.prepareVerifyMacV2(passphrase);
|
||||||
|
} else {
|
||||||
|
mac = this.prepareVerifyMacV3();
|
||||||
|
}
|
||||||
|
this.verify(mac);
|
||||||
} finally {
|
} finally {
|
||||||
PasswordUtils.blankOut(passphrase);
|
PasswordUtils.blankOut(passphrase);
|
||||||
}
|
}
|
||||||
@@ -254,85 +270,157 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a passphrase into a key, by following the convention that PuTTY
|
* Converts a passphrase into a key, by following the convention that PuTTY
|
||||||
* uses.
|
* uses. Only PuTTY v1/v2 key files
|
||||||
* <p/>
|
* <p><p/>
|
||||||
* <p/>
|
|
||||||
* This is used to decrypt the private key when it's encrypted.
|
* This is used to decrypt the private key when it's encrypted.
|
||||||
*/
|
*/
|
||||||
private byte[] toKey(final String passphrase) throws IOException {
|
private void initCipher(final char[] passphrase, Cipher cipher) throws IOException, InvalidAlgorithmParameterException, InvalidKeyException {
|
||||||
// The field Key-Derivation has been introduced with Putty v3 key file format
|
// The field Key-Derivation has been introduced with Putty v3 key file format
|
||||||
// The only available formats are "Argon2i" "Argon2d" and "Argon2id"
|
// For v3 the algorithms are "Argon2i" "Argon2d" and "Argon2id"
|
||||||
String keyDerivation = headers.get("Key-Derivation");
|
String kdfAlgorithm = headers.get("Key-Derivation");
|
||||||
if (keyDerivation != null) {
|
if (kdfAlgorithm != null) {
|
||||||
throw new IOException(String.format("Unsupported key derivation function: %s", keyDerivation));
|
kdfAlgorithm = kdfAlgorithm.toLowerCase();
|
||||||
|
byte[] keyData = this.argon2(kdfAlgorithm, passphrase);
|
||||||
|
if (keyData == null) {
|
||||||
|
throw new IOException(String.format("Unsupported key derivation function: %s", kdfAlgorithm));
|
||||||
|
}
|
||||||
|
byte[] key = new byte[32];
|
||||||
|
byte[] iv = new byte[16];
|
||||||
|
byte[] tag = new byte[32]; // Hmac key
|
||||||
|
System.arraycopy(keyData, 0, key, 0, 32);
|
||||||
|
System.arraycopy(keyData, 32, iv, 0, 16);
|
||||||
|
System.arraycopy(keyData, 48, tag, 0, 32);
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"),
|
||||||
|
new IvParameterSpec(iv));
|
||||||
|
verifyHmac = tag;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Key file format v1 + v2
|
||||||
try {
|
try {
|
||||||
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
||||||
|
|
||||||
// The encryption key is derived from the passphrase by means of a succession of
|
// The encryption key is derived from the passphrase by means of a succession of
|
||||||
// SHA-1 hashes.
|
// SHA-1 hashes.
|
||||||
|
byte[] encodedPassphrase = PasswordUtils.toByteArray(passphrase);
|
||||||
|
|
||||||
// Sequence number 0
|
// Sequence number 0
|
||||||
digest.update(new byte[] { 0, 0, 0, 0 });
|
digest.update(new byte[]{0, 0, 0, 0});
|
||||||
digest.update(passphrase.getBytes());
|
digest.update(encodedPassphrase);
|
||||||
byte[] key1 = digest.digest();
|
byte[] key1 = digest.digest();
|
||||||
|
|
||||||
// Sequence number 1
|
// Sequence number 1
|
||||||
digest.update(new byte[] { 0, 0, 0, 1 });
|
digest.update(new byte[]{0, 0, 0, 1});
|
||||||
digest.update(passphrase.getBytes());
|
digest.update(encodedPassphrase);
|
||||||
byte[] key2 = digest.digest();
|
byte[] key2 = digest.digest();
|
||||||
|
|
||||||
byte[] r = new byte[32];
|
Arrays.fill(encodedPassphrase, (byte) 0);
|
||||||
System.arraycopy(key1, 0, r, 0, 20);
|
|
||||||
System.arraycopy(key2, 0, r, 20, 12);
|
byte[] expanded = new byte[32];
|
||||||
|
System.arraycopy(key1, 0, expanded, 0, 20);
|
||||||
|
System.arraycopy(key2, 0, expanded, 20, 12);
|
||||||
|
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(expanded, 0, 32, "AES"),
|
||||||
|
new IvParameterSpec(new byte[16])); // initial vector=0
|
||||||
|
|
||||||
return r;
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
} catch (NoSuchAlgorithmException e) {
|
||||||
throw new IOException(e.getMessage(), e);
|
throw new IOException(e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verify the MAC.
|
* Uses BouncyCastle Argon2 implementation
|
||||||
*/
|
*/
|
||||||
private void verify(final String passphrase) throws IOException {
|
private byte[] argon2(String algorithm, final char[] passphrase) throws IOException {
|
||||||
|
int type;
|
||||||
|
if ("argon2i".equals(algorithm)) {
|
||||||
|
type = Argon2Parameters.ARGON2_i;
|
||||||
|
} else if ("argon2d".equals(algorithm)) {
|
||||||
|
type = Argon2Parameters.ARGON2_d;
|
||||||
|
} else if ("argon2id".equals(algorithm)) {
|
||||||
|
type = Argon2Parameters.ARGON2_id;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
byte[] salt = Hex.decode(headers.get("Argon2-Salt"));
|
||||||
|
int iterations = Integer.parseInt(headers.get("Argon2-Passes"));
|
||||||
|
int memory = Integer.parseInt(headers.get("Argon2-Memory"));
|
||||||
|
int parallelism = Integer.parseInt(headers.get("Argon2-Parallelism"));
|
||||||
|
|
||||||
|
Argon2Parameters a2p = new Argon2Parameters.Builder(type)
|
||||||
|
.withVersion(Argon2Parameters.ARGON2_VERSION_13)
|
||||||
|
.withIterations(iterations)
|
||||||
|
.withMemoryAsKB(memory)
|
||||||
|
.withParallelism(parallelism)
|
||||||
|
.withSalt(salt).build();
|
||||||
|
|
||||||
|
Argon2BytesGenerator generator = new Argon2BytesGenerator();
|
||||||
|
generator.init(a2p);
|
||||||
|
byte[] output = new byte[80];
|
||||||
|
int bytes = generator.generateBytes(passphrase, output);
|
||||||
|
if (bytes != output.length) {
|
||||||
|
throw new IOException("Failed to generate key via Argon2");
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify the MAC (only required for v1/v2 keys. v3 keys are automatically
|
||||||
|
* verified as part of the decryption process.
|
||||||
|
*/
|
||||||
|
private void verify(final Mac mac) throws IOException {
|
||||||
|
final ByteArrayOutputStream out = new ByteArrayOutputStream(256);
|
||||||
|
final DataOutputStream data = new DataOutputStream(out);
|
||||||
|
// name of algorithm
|
||||||
|
String keyType = this.getType().toString();
|
||||||
|
data.writeInt(keyType.length());
|
||||||
|
data.writeBytes(keyType);
|
||||||
|
|
||||||
|
data.writeInt(headers.get("Encryption").length());
|
||||||
|
data.writeBytes(headers.get("Encryption"));
|
||||||
|
|
||||||
|
data.writeInt(headers.get("Comment").length());
|
||||||
|
data.writeBytes(headers.get("Comment"));
|
||||||
|
|
||||||
|
data.writeInt(publicKey.length);
|
||||||
|
data.write(publicKey);
|
||||||
|
|
||||||
|
data.writeInt(privateKey.length);
|
||||||
|
data.write(privateKey);
|
||||||
|
|
||||||
|
final String encoded = Hex.toHexString(mac.doFinal(out.toByteArray()));
|
||||||
|
final String reference = headers.get("Private-MAC");
|
||||||
|
if (!encoded.equals(reference)) {
|
||||||
|
throw new IOException("Invalid passphrase");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Mac prepareVerifyMacV2(final char[] passphrase) throws IOException {
|
||||||
|
// The key to the MAC is itself a SHA-1 hash of (v1/v2 key only):
|
||||||
try {
|
try {
|
||||||
// The key to the MAC is itself a SHA-1 hash of (v1/v2 key only):
|
|
||||||
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
||||||
digest.update("putty-private-key-file-mac-key".getBytes());
|
digest.update("putty-private-key-file-mac-key".getBytes());
|
||||||
if (passphrase != null) {
|
if (passphrase != null) {
|
||||||
digest.update(passphrase.getBytes());
|
byte[] encodedPassphrase = PasswordUtils.toByteArray(passphrase);
|
||||||
|
digest.update(encodedPassphrase);
|
||||||
|
Arrays.fill(encodedPassphrase, (byte) 0);
|
||||||
}
|
}
|
||||||
final byte[] key = digest.digest();
|
final byte[] key = digest.digest();
|
||||||
|
Mac mac = Mac.getInstance("HmacSHA1");
|
||||||
final Mac mac = Mac.getInstance("HmacSHA1");
|
|
||||||
mac.init(new SecretKeySpec(key, 0, 20, mac.getAlgorithm()));
|
mac.init(new SecretKeySpec(key, 0, 20, mac.getAlgorithm()));
|
||||||
|
return mac;
|
||||||
|
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
|
||||||
|
throw new IOException(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
private Mac prepareVerifyMacV3() throws IOException {
|
||||||
final DataOutputStream data = new DataOutputStream(out);
|
// for v3 keys the hMac key is included in the Argon output
|
||||||
// name of algorithm
|
try {
|
||||||
String keyType = this.getType().toString();
|
Mac mac = Mac.getInstance("HmacSHA256");
|
||||||
data.writeInt(keyType.length());
|
mac.init(new SecretKeySpec(this.verifyHmac, 0, 32, mac.getAlgorithm()));
|
||||||
data.writeBytes(keyType);
|
return mac;
|
||||||
|
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
|
||||||
data.writeInt(headers.get("Encryption").length());
|
|
||||||
data.writeBytes(headers.get("Encryption"));
|
|
||||||
|
|
||||||
data.writeInt(headers.get("Comment").length());
|
|
||||||
data.writeBytes(headers.get("Comment"));
|
|
||||||
|
|
||||||
data.writeInt(publicKey.length);
|
|
||||||
data.write(publicKey);
|
|
||||||
|
|
||||||
data.writeInt(privateKey.length);
|
|
||||||
data.write(privateKey);
|
|
||||||
|
|
||||||
final String encoded = Hex.toHexString(mac.doFinal(out.toByteArray()));
|
|
||||||
final String reference = headers.get("Private-MAC");
|
|
||||||
if (!encoded.equals(reference)) {
|
|
||||||
throw new IOException("Invalid passphrase");
|
|
||||||
}
|
|
||||||
} catch (GeneralSecurityException e) {
|
|
||||||
throw new IOException(e.getMessage(), e);
|
throw new IOException(e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -340,17 +428,21 @@ public class PuTTYKeyFile extends BaseFileKeyProvider {
|
|||||||
/**
|
/**
|
||||||
* Decrypt private key
|
* Decrypt private key
|
||||||
*
|
*
|
||||||
|
* @param privateKey the SSH private key to be decrypted
|
||||||
* @param passphrase To decrypt
|
* @param passphrase To decrypt
|
||||||
*/
|
*/
|
||||||
private byte[] decrypt(final byte[] key, final String passphrase) throws IOException {
|
private byte[] decrypt(final byte[] privateKey, final char[] passphrase) throws IOException {
|
||||||
try {
|
try {
|
||||||
final Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
|
final Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
|
||||||
final byte[] expanded = this.toKey(passphrase);
|
this.initCipher(passphrase, cipher);
|
||||||
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(expanded, 0, 32, "AES"),
|
return cipher.doFinal(privateKey);
|
||||||
new IvParameterSpec(new byte[16])); // initial vector=0
|
|
||||||
return cipher.doFinal(key);
|
|
||||||
} catch (GeneralSecurityException e) {
|
} catch (GeneralSecurityException e) {
|
||||||
throw new IOException(e.getMessage(), e);
|
throw new IOException(e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getKeyFileVersion() {
|
||||||
|
return keyFileVersion;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package net.schmizz.sshj.userauth.password;
|
package net.schmizz.sshj.userauth.password;
|
||||||
|
|
||||||
|
import java.nio.CharBuffer;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
/** Static utility method and factories */
|
/** Static utility method and factories */
|
||||||
@@ -54,4 +56,14 @@ public class PasswordUtils {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a password to a UTF-8 encoded byte array
|
||||||
|
*
|
||||||
|
* @param password
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static byte[] toByteArray(char[] password) {
|
||||||
|
CharBuffer charBuffer = CharBuffer.wrap(password);
|
||||||
|
return StandardCharsets.UTF_8.encode(charBuffer).array();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,20 +18,18 @@ package net.schmizz.sshj.keyprovider;
|
|||||||
import com.hierynomus.sshj.userauth.keyprovider.OpenSSHKeyV1KeyFile;
|
import com.hierynomus.sshj.userauth.keyprovider.OpenSSHKeyV1KeyFile;
|
||||||
import net.schmizz.sshj.userauth.keyprovider.PKCS8KeyFile;
|
import net.schmizz.sshj.userauth.keyprovider.PKCS8KeyFile;
|
||||||
import net.schmizz.sshj.userauth.keyprovider.PuTTYKeyFile;
|
import net.schmizz.sshj.userauth.keyprovider.PuTTYKeyFile;
|
||||||
import net.schmizz.sshj.userauth.password.PasswordFinder;
|
import net.schmizz.sshj.util.UnitTestPasswordFinder;
|
||||||
import net.schmizz.sshj.userauth.password.Resource;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
import java.security.PrivateKey;
|
import java.security.interfaces.RSAPrivateKey;
|
||||||
|
import java.security.interfaces.RSAPublicKey;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
import static org.junit.Assert.fail;
|
|
||||||
|
|
||||||
public class PuTTYKeyFileTest {
|
public class PuTTYKeyFileTest {
|
||||||
|
|
||||||
@@ -230,17 +228,17 @@ public class PuTTYKeyFileTest {
|
|||||||
"Private-MAC: b200c6d801fc8dd8f84a14afc3b94d9f9bb2df90\n";
|
"Private-MAC: b200c6d801fc8dd8f84a14afc3b94d9f9bb2df90\n";
|
||||||
|
|
||||||
final static String v3_ecdsa = "PuTTY-User-Key-File-3: ecdsa-sha2-nistp256\n" +
|
final static String v3_ecdsa = "PuTTY-User-Key-File-3: ecdsa-sha2-nistp256\n" +
|
||||||
"Encryption: none\n" +
|
"Encryption: none\n" +
|
||||||
"Comment: ecdsa-key-20210819\n" +
|
"Comment: ecdsa-key-20210819\n" +
|
||||||
"Public-Lines: 3\n" +
|
"Public-Lines: 3\n" +
|
||||||
"AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBP5mbdlgVmkw\n" +
|
"AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBP5mbdlgVmkw\n" +
|
||||||
"LzDkznoY8TXKnok/mlMkpk8FELFNSECnXNdtZ4B8+Bpqnvchhk/jY/0tUU98lFxt\n" +
|
"LzDkznoY8TXKnok/mlMkpk8FELFNSECnXNdtZ4B8+Bpqnvchhk/jY/0tUU98lFxt\n" +
|
||||||
"JR0o0l8B5y0=\n" +
|
"JR0o0l8B5y0=\n" +
|
||||||
"Private-Lines: 1\n" +
|
"Private-Lines: 1\n" +
|
||||||
"AAAAIEblmwyKaGuvc6dLgNeHsc1BuZeQORTSxBF5SBLNyjYc\n" +
|
"AAAAIEblmwyKaGuvc6dLgNeHsc1BuZeQORTSxBF5SBLNyjYc\n" +
|
||||||
"Private-MAC: e1aed15a209f48fdaa5228640f1109a7740340764a96f97ec6023da7f92d07ea";
|
"Private-MAC: e1aed15a209f48fdaa5228640f1109a7740340764a96f97ec6023da7f92d07ea";
|
||||||
|
|
||||||
final static String v3_rsa_encrypted = "PuTTY-User-Key-File-3: ssh-rsa\n" +
|
final static String v3_rsa_argon2id = "PuTTY-User-Key-File-3: ssh-rsa\n" +
|
||||||
"Encryption: aes256-cbc\n" +
|
"Encryption: aes256-cbc\n" +
|
||||||
"Comment: rsa-key-20210926\n" +
|
"Comment: rsa-key-20210926\n" +
|
||||||
"Public-Lines: 6\n" +
|
"Public-Lines: 6\n" +
|
||||||
@@ -272,6 +270,70 @@ public class PuTTYKeyFileTest {
|
|||||||
"6tV3Z/GJ5aQJFeMYPOq69ktXRLAWr800822NwEStcxtQHTWbaTk7dxh8+0xwlCgI\n" +
|
"6tV3Z/GJ5aQJFeMYPOq69ktXRLAWr800822NwEStcxtQHTWbaTk7dxh8+0xwlCgI\n" +
|
||||||
"Private-MAC: 582dea09758afd93a8e248abce358287d384e5ee36d21515ffcc0d42d8c5d86a\n";
|
"Private-MAC: 582dea09758afd93a8e248abce358287d384e5ee36d21515ffcc0d42d8c5d86a\n";
|
||||||
|
|
||||||
|
final static String v3_rsa_argon2d = "PuTTY-User-Key-File-3: ssh-rsa\n" +
|
||||||
|
"Encryption: aes256-cbc\n" +
|
||||||
|
"Comment: rsa-key-20210926\n" +
|
||||||
|
"Public-Lines: 6\n" +
|
||||||
|
"AAAAB3NzaC1yc2EAAAADAQABAAABAQCBjWQHMpKAQnU3vZZF/iHn4RA867Ox+U03\n" +
|
||||||
|
"/GOHivW0SgGIQbhKcSSWvTzYOE+GQdtX9T2KJxr76z/lB4nghkcWkpLoQW91gNBf\n" +
|
||||||
|
"PUagMvaBxKXC8cNqaMm99uw5KpRg8SpTJWxwYPlQtzmyxav0PRFeOMSsiRsnjNuX\n" +
|
||||||
|
"polMDSu6vmkkuKrPzvinPZbsXoZeMybcm1gn2Zq+7ik4us0icaGxRJRuF+nVqYag\n" +
|
||||||
|
"EmO9jmQoytyqoNWzvPYEh/dh85hESwtIKXiaMOjQg52dW5BuELPGV7ZxaKRK7Znw\n" +
|
||||||
|
"RGW6CtoGYulo0mJz5IZslDrRK/EK2bSGDbrlAcYaajROB6aBDyaJ\n" +
|
||||||
|
"Key-Derivation: Argon2d\n" +
|
||||||
|
"Argon2-Memory: 2048\n" +
|
||||||
|
"Argon2-Passes: 5\n" +
|
||||||
|
"Argon2-Parallelism: 3\n" +
|
||||||
|
"Argon2-Salt: 5fa1eb89e9eac0cc562c59bc648cacea\n" +
|
||||||
|
"Private-Lines: 14\n" +
|
||||||
|
"CCLbHvtNdkMNqOrSM+CNF874xTjDs01+UanZX7pHmIA94nAbb9ofeEHPcw7pCCWq\n" +
|
||||||
|
"mxj7GK8BnsEQXIS1yCnRT6yCi1d68FpdXN2QvhlbWpEuzrmw4q71XpCwYpZ3KERo\n" +
|
||||||
|
"+/o7X2Pi4qhfaS+fgBAl2VwAiHdN2KQFewj6MWJqP2/GaegKyvnZue/3e+v/Edag\n" +
|
||||||
|
"DCbODfNhfirISUlw5U3SxqmIdrFT+2DKbpVTCLQwTeXL+fmzdYYvjOGQq6Kx7E9L\n" +
|
||||||
|
"iWf1aLoBZCWfN5gpMxD1F1tX1nBXmFMG8aW+lOr3+BxLMUAtjRQVsmc6Lyqb1RdV\n" +
|
||||||
|
"eyZp1W0R2+HKmwm59WUQK46HMnXdkwUqArb28VpBE61gj+KMWna9TJP6aJTF2N6m\n" +
|
||||||
|
"0Wv8D9WCGOrOC+IqnkfkfdSLkupu6PyyhiS69IR9b6vAyDYFxhtlEx6qZpjSKLYr\n" +
|
||||||
|
"X11I223yPAmSoO1X24RNPpo1uU4k8NfZWH0ZICY3YZ0K3PnETNGd5C38OSptQFor\n" +
|
||||||
|
"9aY1oV/1VencX/CmGXaQHsV5UJ/SnV78+PPSv3pEeQmd2ljmSx3kTL1BX91n4/Mc\n" +
|
||||||
|
"jNxE3kMXJ+6DD6OGGU0VbVmYBCrFDD4Mfj8yyLKOjJgEZubCLaZoI7WhDk4qZcui\n" +
|
||||||
|
"hzPt1tshrjIN6VKubqg84BVOWmJ3MmDD76ci9d5ILeAm4zzsliuagSLa+Y6t03hs\n" +
|
||||||
|
"PmRnFSiCv1zrqLl20PcrPEsifGeC/o1839/9E0Gywy/JDjlbucxfU9qHOntnqQJM\n" +
|
||||||
|
"8cAjXyuzgkKC5yzk/Py3VnjWegENrfM5Zf/eXFYFzD0cIA0ou2ap+Dcln14ckGFZ\n" +
|
||||||
|
"kir9AVgxyOiQikD8za+QjZ2rLeuzODe9mKPPKitI4npanpGcWRl+RPCG4t9poacO\n" +
|
||||||
|
"Private-MAC: d08aebc419131c109bbf8c200848f47eafedab9286b372c3155e8dc27e6b84cd\n";
|
||||||
|
|
||||||
|
final static String v3_rsa_argon2i = "PuTTY-User-Key-File-3: ssh-rsa\n" +
|
||||||
|
"Encryption: aes256-cbc\n" +
|
||||||
|
"Comment: rsa-key-20210926\n" +
|
||||||
|
"Public-Lines: 6\n" +
|
||||||
|
"AAAAB3NzaC1yc2EAAAADAQABAAABAQCBjWQHMpKAQnU3vZZF/iHn4RA867Ox+U03\n" +
|
||||||
|
"/GOHivW0SgGIQbhKcSSWvTzYOE+GQdtX9T2KJxr76z/lB4nghkcWkpLoQW91gNBf\n" +
|
||||||
|
"PUagMvaBxKXC8cNqaMm99uw5KpRg8SpTJWxwYPlQtzmyxav0PRFeOMSsiRsnjNuX\n" +
|
||||||
|
"polMDSu6vmkkuKrPzvinPZbsXoZeMybcm1gn2Zq+7ik4us0icaGxRJRuF+nVqYag\n" +
|
||||||
|
"EmO9jmQoytyqoNWzvPYEh/dh85hESwtIKXiaMOjQg52dW5BuELPGV7ZxaKRK7Znw\n" +
|
||||||
|
"RGW6CtoGYulo0mJz5IZslDrRK/EK2bSGDbrlAcYaajROB6aBDyaJ\n" +
|
||||||
|
"Key-Derivation: Argon2i\n" +
|
||||||
|
"Argon2-Memory: 1024\n" +
|
||||||
|
"Argon2-Passes: 5\n" +
|
||||||
|
"Argon2-Parallelism: 2\n" +
|
||||||
|
"Argon2-Salt: 2845c351de77c5aa9604e407ca830136\n" +
|
||||||
|
"Private-Lines: 14\n" +
|
||||||
|
"Ws3CZMJ0xYa/W6s0YZqn4j8ihXK81lw88iuXrmzWu+L88+RVGTBGvOvmE0oqLsMw\n" +
|
||||||
|
"YPIi7/eOaik1jZ+dnnD/PeJbVOqch7z2fSK1cVXMyNggPvFBQVjtxrFRhvGtIC0R\n" +
|
||||||
|
"5py8Z7Cfi0W9N/xyjHIvNrGwuvUQpBKeK1C/zYweQJF/clBSovnV/sGGRbtEd+jk\n" +
|
||||||
|
"rY8svRKSvX0HY0P4xftwH+E40XZhUdG2JetleCNIw0ohShuCSiO1fauxI3Az/i2J\n" +
|
||||||
|
"Ef3pRfMLCE91QW4/3nY2ofK6yyufNhyFSjqIaDkQUNBi4EYG1W2/29mK9zLpfa+Z\n" +
|
||||||
|
"eiujzOZJfI8QPar7gTp2sdrq7ND2YUniatwqpq9+vefTkWvMEhwuNAGvfRWgJ2qq\n" +
|
||||||
|
"IbB/EWtvNj8vA3z3M2j0ksMRvJSGpU1n8MKVdWe5PSjvpMiCaqtOTtrP3iqS+bwJ\n" +
|
||||||
|
"WjhV+JVod5RE0fCXnBcCkE8XdSu20m04aRIVHJvnIaKH7vZXThDdG9AhpSrUcvWM\n" +
|
||||||
|
"OVD5q0L9W9wcVQzN7XtQhTEjm3zja+tOo0gYn0Z/497kkxdL/g/su5kpPQsbbsLF\n" +
|
||||||
|
"0ROS5U2GZX0Le+QVg6hGIfqskBoCQp+ErTXFzIu+0//MoaZSACtW48ljeIpDj0fG\n" +
|
||||||
|
"v2Fhc9tbpTJKvQh6wlm9gkMBSV+XcRWUMh5zBPecmR6/v9O4/MCsOse89MNs4LxL\n" +
|
||||||
|
"sLRUABdjziKnjomq/1FlozlGGfF+v+VLhjjc1xq5ms+BEqkXUsWoJl8NNST6NqkN\n" +
|
||||||
|
"2T4nFzZA6b+RwFJFqYHF+BvgkQ5j0hEbXo1qlqKIf3Vk+/rouPkLyUIiHxZxdX4m\n" +
|
||||||
|
"P/LtnH79FPDQFbFl6826Ui1TPISAf3pTwKFI43HgKRrya3F5GPeQphsZHlu155JO\n" +
|
||||||
|
"Private-MAC: 1be8357d497fd4d641ce50a142c5a91ef3b0279355d2996e0c1f13e376394301\n";
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test2048() throws Exception {
|
public void test2048() throws Exception {
|
||||||
PuTTYKeyFile key = new PuTTYKeyFile();
|
PuTTYKeyFile key = new PuTTYKeyFile();
|
||||||
@@ -319,17 +381,8 @@ public class PuTTYKeyFileTest {
|
|||||||
// -o src/test/resources/keytypes/test_ed25519_puttygen_protected.ppk \
|
// -o src/test/resources/keytypes/test_ed25519_puttygen_protected.ppk \
|
||||||
// --new-passphrase <(echo 123456)
|
// --new-passphrase <(echo 123456)
|
||||||
PuTTYKeyFile key = new PuTTYKeyFile();
|
PuTTYKeyFile key = new PuTTYKeyFile();
|
||||||
key.init(new File("src/test/resources/keytypes/test_ed25519_puttygen_protected.ppk"), new PasswordFinder() {
|
key.init(new File("src/test/resources/keytypes/test_ed25519_puttygen_protected.ppk"),
|
||||||
@Override
|
new UnitTestPasswordFinder("123456"));
|
||||||
public char[] reqPassword(Resource<?> resource) {
|
|
||||||
return "123456".toCharArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean shouldRetry(Resource<?> resource) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
assertNotNull(key.getPrivate());
|
assertNotNull(key.getPrivate());
|
||||||
assertNotNull(key.getPublic());
|
assertNotNull(key.getPublic());
|
||||||
|
|
||||||
@@ -388,57 +441,62 @@ public class PuTTYKeyFileTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testV3Key() throws Exception {
|
public void testV3KeyArgon2id() throws Exception {
|
||||||
PuTTYKeyFile key = new PuTTYKeyFile();
|
PuTTYKeyFile key = new PuTTYKeyFile();
|
||||||
key.init(new StringReader(v3_ecdsa));
|
key.init(new StringReader(v3_ecdsa));
|
||||||
assertNotNull(key.getPrivate());
|
assertNotNull(key.getPrivate());
|
||||||
assertNotNull(key.getPublic());
|
assertNotNull(key.getPublic());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Reading an encrypted Putty v3 key requires an Argon2i/Argon2d/Argon2id
|
|
||||||
* implementation.
|
|
||||||
* Putty v3 keys additionally use a different algorithm for generating the "Private-MAC"
|
|
||||||
*/
|
|
||||||
@Test
|
@Test
|
||||||
public void testRSAv3EncryptedKey() throws Exception {
|
public void testRSAv3EncryptedKeyArgon2id() throws Exception {
|
||||||
PuTTYKeyFile key = new PuTTYKeyFile();
|
PuTTYKeyFile key = new PuTTYKeyFile();
|
||||||
key.init(new StringReader(v3_rsa_encrypted), new PasswordFinder() {
|
key.init(new StringReader(v3_rsa_argon2id), new UnitTestPasswordFinder("changeit"));
|
||||||
@Override
|
assertNotNull(key.getPrivate());
|
||||||
public char[] reqPassword(Resource<?> resource) {
|
assertNotNull(key.getPublic());
|
||||||
return "changeit".toCharArray();
|
OpenSSHKeyV1KeyFile referenceKey = new OpenSSHKeyV1KeyFile();
|
||||||
}
|
referenceKey.init(new File("src/test/resources/keytypes/test_rsa_putty_priv.openssh2"));
|
||||||
|
RSAPrivateKey loadedPrivate = (RSAPrivateKey) key.getPrivate();
|
||||||
|
RSAPrivateKey referencePrivate = (RSAPrivateKey) referenceKey.getPrivate();
|
||||||
|
assertEquals(referencePrivate.getPrivateExponent(), loadedPrivate.getPrivateExponent());
|
||||||
|
assertEquals(referencePrivate.getModulus(), loadedPrivate.getModulus());
|
||||||
|
assertEquals(referencePrivate.getModulus(), ((RSAPublicKey) key.getPublic()).getModulus());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Test
|
||||||
public boolean shouldRetry(Resource<?> resource) {
|
public void testRSAv3EncryptedKeyArgon2d() throws Exception {
|
||||||
return false;
|
PuTTYKeyFile key = new PuTTYKeyFile();
|
||||||
}
|
key.init(new StringReader(v3_rsa_argon2d), new UnitTestPasswordFinder("changeit"));
|
||||||
});
|
assertNotNull(key.getPrivate());
|
||||||
try {
|
assertNotNull(key.getPublic());
|
||||||
PrivateKey privateKey = key.getPrivate();
|
OpenSSHKeyV1KeyFile referenceKey = new OpenSSHKeyV1KeyFile();
|
||||||
fail("IOException expected as encrypted Putty v3 keys are not yet supported");
|
referenceKey.init(new File("src/test/resources/keytypes/test_rsa_putty_priv.openssh2"));
|
||||||
} catch (IOException e) {
|
RSAPrivateKey loadedPrivate = (RSAPrivateKey) key.getPrivate();
|
||||||
assertTrue(e.getMessage().startsWith("Unsupported key derivation function"));
|
RSAPrivateKey referencePrivate = (RSAPrivateKey) referenceKey.getPrivate();
|
||||||
}
|
assertEquals(referencePrivate.getPrivateExponent(), loadedPrivate.getPrivateExponent());
|
||||||
// assertNotNull(key.getPrivate());
|
assertEquals(referencePrivate.getModulus(), loadedPrivate.getModulus());
|
||||||
// assertNotNull(key.getPublic());
|
assertEquals(referencePrivate.getModulus(), ((RSAPublicKey) key.getPublic()).getModulus());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRSAv3EncryptedKeyArgon2i() throws Exception {
|
||||||
|
PuTTYKeyFile key = new PuTTYKeyFile();
|
||||||
|
key.init(new StringReader(v3_rsa_argon2i), new UnitTestPasswordFinder("changeit"));
|
||||||
|
assertNotNull(key.getPrivate());
|
||||||
|
assertNotNull(key.getPublic());
|
||||||
|
OpenSSHKeyV1KeyFile referenceKey = new OpenSSHKeyV1KeyFile();
|
||||||
|
referenceKey.init(new File("src/test/resources/keytypes/test_rsa_putty_priv.openssh2"));
|
||||||
|
RSAPrivateKey loadedPrivate = (RSAPrivateKey) key.getPrivate();
|
||||||
|
RSAPrivateKey referencePrivate = (RSAPrivateKey) referenceKey.getPrivate();
|
||||||
|
assertEquals(referencePrivate.getPrivateExponent(), loadedPrivate.getPrivateExponent());
|
||||||
|
assertEquals(referencePrivate.getModulus(), loadedPrivate.getModulus());
|
||||||
|
assertEquals(referencePrivate.getModulus(), ((RSAPublicKey) key.getPublic()).getModulus());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCorrectPassphraseRsa() throws Exception {
|
public void testCorrectPassphraseRsa() throws Exception {
|
||||||
PuTTYKeyFile key = new PuTTYKeyFile();
|
PuTTYKeyFile key = new PuTTYKeyFile();
|
||||||
key.init(new StringReader(ppk1024_passphrase), new PasswordFinder() {
|
key.init(new StringReader(ppk1024_passphrase), new UnitTestPasswordFinder("123456"));
|
||||||
@Override
|
|
||||||
public char[] reqPassword(Resource<?> resource) {
|
|
||||||
// correct passphrase
|
|
||||||
return "123456".toCharArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean shouldRetry(Resource<?> resource) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Install JCE Unlimited Strength Jurisdiction Policy Files if we get java.security.InvalidKeyException: Illegal key size
|
// Install JCE Unlimited Strength Jurisdiction Policy Files if we get java.security.InvalidKeyException: Illegal key size
|
||||||
assertNotNull(key.getPrivate());
|
assertNotNull(key.getPrivate());
|
||||||
assertNotNull(key.getPublic());
|
assertNotNull(key.getPublic());
|
||||||
@@ -447,18 +505,8 @@ public class PuTTYKeyFileTest {
|
|||||||
@Test(expected = IOException.class)
|
@Test(expected = IOException.class)
|
||||||
public void testWrongPassphraseRsa() throws Exception {
|
public void testWrongPassphraseRsa() throws Exception {
|
||||||
PuTTYKeyFile key = new PuTTYKeyFile();
|
PuTTYKeyFile key = new PuTTYKeyFile();
|
||||||
key.init(new StringReader(ppk1024_passphrase), new PasswordFinder() {
|
key.init(new StringReader(ppk1024_passphrase),
|
||||||
@Override
|
new UnitTestPasswordFinder("egfsdgdfgsdfsdfasfs523534dgdsgdfa"));
|
||||||
public char[] reqPassword(Resource<?> resource) {
|
|
||||||
// wrong passphrase
|
|
||||||
return "egfsdgdfgsdfsdfasfs523534dgdsgdfa".toCharArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean shouldRetry(Resource<?> resource) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
assertNotNull(key.getPublic());
|
assertNotNull(key.getPublic());
|
||||||
assertNull(key.getPrivate());
|
assertNull(key.getPrivate());
|
||||||
}
|
}
|
||||||
@@ -466,18 +514,7 @@ public class PuTTYKeyFileTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testCorrectPassphraseDsa() throws Exception {
|
public void testCorrectPassphraseDsa() throws Exception {
|
||||||
PuTTYKeyFile key = new PuTTYKeyFile();
|
PuTTYKeyFile key = new PuTTYKeyFile();
|
||||||
key.init(new StringReader(ppkdsa_passphrase), new PasswordFinder() {
|
key.init(new StringReader(ppkdsa_passphrase), new UnitTestPasswordFinder("secret"));
|
||||||
@Override
|
|
||||||
public char[] reqPassword(Resource<?> resource) {
|
|
||||||
// correct passphrase
|
|
||||||
return "secret".toCharArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean shouldRetry(Resource<?> resource) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Install JCE Unlimited Strength Jurisdiction Policy Files if we get java.security.InvalidKeyException: Illegal key size
|
// Install JCE Unlimited Strength Jurisdiction Policy Files if we get java.security.InvalidKeyException: Illegal key size
|
||||||
assertNotNull(key.getPrivate());
|
assertNotNull(key.getPrivate());
|
||||||
assertNotNull(key.getPublic());
|
assertNotNull(key.getPublic());
|
||||||
@@ -486,18 +523,8 @@ public class PuTTYKeyFileTest {
|
|||||||
@Test(expected = IOException.class)
|
@Test(expected = IOException.class)
|
||||||
public void testWrongPassphraseDsa() throws Exception {
|
public void testWrongPassphraseDsa() throws Exception {
|
||||||
PuTTYKeyFile key = new PuTTYKeyFile();
|
PuTTYKeyFile key = new PuTTYKeyFile();
|
||||||
key.init(new StringReader(ppkdsa_passphrase), new PasswordFinder() {
|
key.init(new StringReader(ppkdsa_passphrase),
|
||||||
@Override
|
new UnitTestPasswordFinder("egfsdgdfgsdfsdfasfs523534dgdsgdfa"));
|
||||||
public char[] reqPassword(Resource<?> resource) {
|
|
||||||
// wrong passphrase
|
|
||||||
return "egfsdgdfgsdfsdfasfs523534dgdsgdfa".toCharArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean shouldRetry(Resource<?> resource) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
assertNotNull(key.getPublic());
|
assertNotNull(key.getPublic());
|
||||||
assertNull(key.getPrivate());
|
assertNull(key.getPrivate());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C)2009 - SSHJ Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package net.schmizz.sshj.util;
|
||||||
|
|
||||||
|
import net.schmizz.sshj.userauth.password.PasswordFinder;
|
||||||
|
import net.schmizz.sshj.userauth.password.Resource;
|
||||||
|
|
||||||
|
public class UnitTestPasswordFinder implements PasswordFinder {
|
||||||
|
|
||||||
|
private final char[] password;
|
||||||
|
|
||||||
|
public UnitTestPasswordFinder(String password) {
|
||||||
|
this.password = password.toCharArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public UnitTestPasswordFinder(char[] password) {
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public char[] reqPassword(Resource<?> resource) {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldRetry(Resource<?> resource) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/test/resources/keytypes/test_rsa_putty_priv.openssh2
Normal file
30
src/test/resources/keytypes/test_rsa_putty_priv.openssh2
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||||
|
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdz
|
||||||
|
c2gtcnNhAAAAAwEAAQAAAQEAgY1kBzKSgEJ1N72WRf4h5+EQPOuzsflNN/xjh4r1
|
||||||
|
tEoBiEG4SnEklr082DhPhkHbV/U9iica++s/5QeJ4IZHFpKS6EFvdYDQXz1GoDL2
|
||||||
|
gcSlwvHDamjJvfbsOSqUYPEqUyVscGD5ULc5ssWr9D0RXjjErIkbJ4zbl6aJTA0r
|
||||||
|
ur5pJLiqz874pz2W7F6GXjMm3JtYJ9mavu4pOLrNInGhsUSUbhfp1amGoBJjvY5k
|
||||||
|
KMrcqqDVs7z2BIf3YfOYREsLSCl4mjDo0IOdnVuQbhCzxle2cWikSu2Z8ERlugra
|
||||||
|
BmLpaNJic+SGbJQ60SvxCtm0hg265QHGGmo0TgemgQ8miQAAA9DskMes7JDHrAAA
|
||||||
|
AAdzc2gtcnNhAAABAQCBjWQHMpKAQnU3vZZF/iHn4RA867Ox+U03/GOHivW0SgGI
|
||||||
|
QbhKcSSWvTzYOE+GQdtX9T2KJxr76z/lB4nghkcWkpLoQW91gNBfPUagMvaBxKXC
|
||||||
|
8cNqaMm99uw5KpRg8SpTJWxwYPlQtzmyxav0PRFeOMSsiRsnjNuXpolMDSu6vmkk
|
||||||
|
uKrPzvinPZbsXoZeMybcm1gn2Zq+7ik4us0icaGxRJRuF+nVqYagEmO9jmQoytyq
|
||||||
|
oNWzvPYEh/dh85hESwtIKXiaMOjQg52dW5BuELPGV7ZxaKRK7ZnwRGW6CtoGYulo
|
||||||
|
0mJz5IZslDrRK/EK2bSGDbrlAcYaajROB6aBDyaJAAAAAwEAAQAAAQBJt9bvcYuD
|
||||||
|
iE2DBlJ4SX+pnpvKzqRV5XJXJTrNafkeOe5dRmhDk9YqIEx7DK/Tya2yg04dSttD
|
||||||
|
9j1Jady+8imJYqZNms59omrvhsKlbdpvRSK9pyx3ZGFHwzXv4ZbFAvX+khD+cW/s
|
||||||
|
yhX+8BREymsTnmHre6kD/FcIGC+QIv57J/snTHLIWRYMvHssgEBAvtjyTmXXurzr
|
||||||
|
9g92uAp/RwpIIiWB695lQbWWv9jwTFl9MinFu0ckRjp8djwvoK54vyWIvzxAOH7X
|
||||||
|
D8t3qmEAuekrf4uo6np3CqkHOU/5SAEs+o0KZ729/pS4fs8vWLi8qRXmRxbPrcvq
|
||||||
|
+uTTEjeQw7gBAAAAgQCtka9gYjdGNVz61o5wQWzHyMsszUZBlZwrXEbOnLYuz9A0
|
||||||
|
dwgb9yRuZFbDpGja5eLMjECua+ZvXu4XLk16SOr/y70Q4WRpv9I/Pk/QTDWepcs9
|
||||||
|
jmjp4IikYwBNBPZQPhnbLJ/9wrALOy0dFOuG76j8/jhoxDCGnxKbxNursWaxUQAA
|
||||||
|
AIEA/zvWbAkmleL4Xc70PtlsUGYkIitT8zb1FYj2L/neW63iSdn+9dPjJ8jCyIhK
|
||||||
|
R11jwlyKoyS765cZtZJCfSf5RkoF7p7I7m3dSXZ2+3ccS9Kv1VvE+mBIxLpySXPh
|
||||||
|
C9rmILzDHXU4VfhQ08Cp4Fd87wZC0Z1PlOeWWS/k9Vqcn0EAAACBAIHw9Z3xfqG3
|
||||||
|
tEuWima0iLbEZhNRjvxqbyCBLG2MoA8EwQN1lMNzpYKP4VxTdkuS8xF2xhDzr8xT
|
||||||
|
Ap0Lb8Q/gr2bZB2a8AsxeuYSlon1svVUDy+IGeD9JLx79lyevwAjcZ8lx6fOyC3E
|
||||||
|
/vMXJ14gHWfzamKvRGLy6UZ4A+t6431JAAAAEHJzYS1rZXktMjAyMTA5MjYBAgME
|
||||||
|
BQYHCAkK
|
||||||
|
-----END OPENSSH PRIVATE KEY-----
|
||||||
Reference in New Issue
Block a user