mirror of
https://github.com/hierynomus/sshj.git
synced 2025-12-07 07:40:55 +03:00
Replace PKCS5 Key File Class with PKCS8 (#793)
* Replaced PKCS5 parsing with PKCS8 - Moved tests for PEM-encoded PKCS1 files to PKCS8 - Removed PKCS5 Key File implementation * Added PKCS8 test to retry password after initial failure Co-authored-by: Jeroen van Erp <jeroen@hierynomus.com>
This commit is contained in:
@@ -36,11 +36,9 @@ import net.schmizz.sshj.transport.kex.Curve25519SHA256;
|
|||||||
import net.schmizz.sshj.transport.kex.DHGexSHA1;
|
import net.schmizz.sshj.transport.kex.DHGexSHA1;
|
||||||
import net.schmizz.sshj.transport.kex.DHGexSHA256;
|
import net.schmizz.sshj.transport.kex.DHGexSHA256;
|
||||||
import net.schmizz.sshj.transport.kex.ECDHNistP;
|
import net.schmizz.sshj.transport.kex.ECDHNistP;
|
||||||
import net.schmizz.sshj.transport.random.BouncyCastleRandom;
|
|
||||||
import net.schmizz.sshj.transport.random.JCERandom;
|
import net.schmizz.sshj.transport.random.JCERandom;
|
||||||
import net.schmizz.sshj.transport.random.SingletonRandomFactory;
|
import net.schmizz.sshj.transport.random.SingletonRandomFactory;
|
||||||
import net.schmizz.sshj.userauth.keyprovider.OpenSSHKeyFile;
|
import net.schmizz.sshj.userauth.keyprovider.OpenSSHKeyFile;
|
||||||
import net.schmizz.sshj.userauth.keyprovider.PKCS5KeyFile;
|
|
||||||
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 org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -162,7 +160,6 @@ public class DefaultConfig
|
|||||||
setFileKeyProviderFactories(
|
setFileKeyProviderFactories(
|
||||||
new OpenSSHKeyV1KeyFile.Factory(),
|
new OpenSSHKeyV1KeyFile.Factory(),
|
||||||
new PKCS8KeyFile.Factory(),
|
new PKCS8KeyFile.Factory(),
|
||||||
new PKCS5KeyFile.Factory(),
|
|
||||||
new OpenSSHKeyFile.Factory(),
|
new OpenSSHKeyFile.Factory(),
|
||||||
new PuTTYKeyFile.Factory());
|
new PuTTYKeyFile.Factory());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -551,7 +551,7 @@ public class SSHClient
|
|||||||
* Creates a {@link KeyProvider} instance from given location on the file system. Currently the following private key files are supported:
|
* Creates a {@link KeyProvider} instance from given location on the file system. Currently the following private key files are supported:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>PKCS8 (OpenSSH uses this format)</li>
|
* <li>PKCS8 (OpenSSH uses this format)</li>
|
||||||
* <li>PKCS5</li>
|
* <li>PEM-encoded PKCS1</li>
|
||||||
* <li>Putty keyfile</li>
|
* <li>Putty keyfile</li>
|
||||||
* <li>openssh-key-v1 (New OpenSSH keyfile format)</li>
|
* <li>openssh-key-v1 (New OpenSSH keyfile format)</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
|
|||||||
@@ -16,10 +16,9 @@
|
|||||||
package net.schmizz.sshj.userauth.keyprovider;
|
package net.schmizz.sshj.userauth.keyprovider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @version $Id:$
|
* Key File Formats
|
||||||
*/
|
*/
|
||||||
public enum KeyFormat {
|
public enum KeyFormat {
|
||||||
PKCS5,
|
|
||||||
PKCS8,
|
PKCS8,
|
||||||
OpenSSH,
|
OpenSSH,
|
||||||
OpenSSHv1,
|
OpenSSHv1,
|
||||||
|
|||||||
@@ -27,9 +27,9 @@ public class KeyProviderUtil {
|
|||||||
* <p/>
|
* <p/>
|
||||||
* Return values are consistent with the {@code NamedFactory} implementations in the {@code keyprovider} package.
|
* Return values are consistent with the {@code NamedFactory} implementations in the {@code keyprovider} package.
|
||||||
*
|
*
|
||||||
* @param location
|
* @param location File Path to key
|
||||||
* @return name of the key file format
|
* @return name of the key file format
|
||||||
* @throws java.io.IOException
|
* @throws java.io.IOException Thrown on file processing failures
|
||||||
*/
|
*/
|
||||||
public static KeyFormat detectKeyFileFormat(File location)
|
public static KeyFormat detectKeyFileFormat(File location)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
@@ -45,7 +45,7 @@ public class KeyProviderUtil {
|
|||||||
* @param privateKey Private key stored in a string
|
* @param privateKey Private key stored in a string
|
||||||
* @param separatePubKey Is the public key stored separately from the private key
|
* @param separatePubKey Is the public key stored separately from the private key
|
||||||
* @return name of the key file format
|
* @return name of the key file format
|
||||||
* @throws java.io.IOException
|
* @throws java.io.IOException Thrown on file processing failures
|
||||||
*/
|
*/
|
||||||
public static KeyFormat detectKeyFileFormat(String privateKey, boolean separatePubKey)
|
public static KeyFormat detectKeyFileFormat(String privateKey, boolean separatePubKey)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
@@ -60,7 +60,7 @@ public class KeyProviderUtil {
|
|||||||
* @param privateKey Private key accessible through a {@code Reader}
|
* @param privateKey Private key accessible through a {@code Reader}
|
||||||
* @param separatePubKey Is the public key stored separately from the private key
|
* @param separatePubKey Is the public key stored separately from the private key
|
||||||
* @return name of the key file format
|
* @return name of the key file format
|
||||||
* @throws java.io.IOException
|
* @throws java.io.IOException Thrown on file processing failures
|
||||||
*/
|
*/
|
||||||
public static KeyFormat detectKeyFileFormat(Reader privateKey, boolean separatePubKey)
|
public static KeyFormat detectKeyFileFormat(Reader privateKey, boolean separatePubKey)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
@@ -94,10 +94,8 @@ public class KeyProviderUtil {
|
|||||||
} else if (separatePubKey) {
|
} else if (separatePubKey) {
|
||||||
// Can delay asking for password since have unencrypted pubkey
|
// Can delay asking for password since have unencrypted pubkey
|
||||||
return KeyFormat.OpenSSH;
|
return KeyFormat.OpenSSH;
|
||||||
} else if (header.contains("BEGIN PRIVATE KEY") || header.contains("BEGIN ENCRYPTED PRIVATE KEY")) {
|
|
||||||
return KeyFormat.PKCS8;
|
|
||||||
} else {
|
} else {
|
||||||
return KeyFormat.PKCS5;
|
return KeyFormat.PKCS8;
|
||||||
}
|
}
|
||||||
} else if (header.startsWith("PuTTY-User-Key-File-")) {
|
} else if (header.startsWith("PuTTY-User-Key-File-")) {
|
||||||
return KeyFormat.PuTTY;
|
return KeyFormat.PuTTY;
|
||||||
|
|||||||
@@ -1,272 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.userauth.keyprovider;
|
|
||||||
|
|
||||||
import com.hierynomus.sshj.common.KeyAlgorithm;
|
|
||||||
import com.hierynomus.sshj.transport.cipher.BlockCiphers;
|
|
||||||
import net.schmizz.sshj.common.Base64;
|
|
||||||
import net.schmizz.sshj.common.ByteArrayUtils;
|
|
||||||
import net.schmizz.sshj.common.IOUtils;
|
|
||||||
import net.schmizz.sshj.common.KeyType;
|
|
||||||
import net.schmizz.sshj.transport.cipher.*;
|
|
||||||
import net.schmizz.sshj.transport.digest.Digest;
|
|
||||||
import net.schmizz.sshj.transport.digest.MD5;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.EOFException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.math.BigInteger;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.CharBuffer;
|
|
||||||
import java.security.*;
|
|
||||||
import java.security.spec.*;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a PKCS5-encoded key file. This is the format typically used by OpenSSH, OpenSSL, Amazon, etc.
|
|
||||||
*/
|
|
||||||
public class PKCS5KeyFile extends BaseFileKeyProvider {
|
|
||||||
|
|
||||||
public static class Factory
|
|
||||||
implements net.schmizz.sshj.common.Factory.Named<FileKeyProvider> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public FileKeyProvider create() {
|
|
||||||
return new PKCS5KeyFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return "PKCS5";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates a format issue with PKCS5 data
|
|
||||||
*/
|
|
||||||
public static class FormatException
|
|
||||||
extends IOException {
|
|
||||||
|
|
||||||
FormatException(String msg) {
|
|
||||||
super(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates a problem decrypting the data
|
|
||||||
*/
|
|
||||||
public static class DecryptException
|
|
||||||
extends IOException {
|
|
||||||
|
|
||||||
DecryptException(String msg) {
|
|
||||||
super(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected byte[] data;
|
|
||||||
|
|
||||||
protected KeyPair readKeyPair()
|
|
||||||
throws IOException {
|
|
||||||
|
|
||||||
BufferedReader reader = new BufferedReader(resource.getReader());
|
|
||||||
try {
|
|
||||||
String line = null;
|
|
||||||
Cipher cipher = new NoneCipher();
|
|
||||||
StringBuffer sb = new StringBuffer();
|
|
||||||
byte[] iv = new byte[0]; // salt
|
|
||||||
while ((line = reader.readLine()) != null) {
|
|
||||||
if (line.startsWith("-----BEGIN ") && line.endsWith(" PRIVATE KEY-----")) {
|
|
||||||
int end = line.length() - 17;
|
|
||||||
if (end > 11) {
|
|
||||||
String s = line.substring(11, line.length() - 17);
|
|
||||||
if ("RSA".equals(s)) {
|
|
||||||
type = KeyType.RSA;
|
|
||||||
} else if ("DSA".equals(s)) {
|
|
||||||
type = KeyType.DSA;
|
|
||||||
} else if ("DSS".equals(s)) {
|
|
||||||
type = KeyType.DSA;
|
|
||||||
} else {
|
|
||||||
throw new FormatException("Unrecognized PKCS5 key type");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new FormatException("Bad header; possibly PKCS8 format?");
|
|
||||||
}
|
|
||||||
} else if (line.startsWith("-----END")) {
|
|
||||||
break;
|
|
||||||
} else if (type != null) {
|
|
||||||
if (line.startsWith("Proc-Type: ")) {
|
|
||||||
if (!"4,ENCRYPTED".equals(line.substring(11))) {
|
|
||||||
throw new FormatException("Unrecognized Proc-Type");
|
|
||||||
}
|
|
||||||
} else if (line.startsWith("DEK-Info: ")) {
|
|
||||||
int ptr = line.indexOf(",");
|
|
||||||
if (ptr == -1) {
|
|
||||||
throw new FormatException("Unrecognized DEK-Info");
|
|
||||||
} else {
|
|
||||||
String algorithm = line.substring(10, ptr);
|
|
||||||
if ("DES-EDE3-CBC".equals(algorithm)) {
|
|
||||||
cipher = BlockCiphers.TripleDESCBC().create();
|
|
||||||
} else if ("AES-128-CBC".equals(algorithm)) {
|
|
||||||
cipher = BlockCiphers.AES128CBC().create();
|
|
||||||
} else if ("AES-192-CBC".equals(algorithm)) {
|
|
||||||
cipher = BlockCiphers.AES192CBC().create();
|
|
||||||
} else if ("AES-256-CBC".equals(algorithm)) {
|
|
||||||
cipher = BlockCiphers.AES256CBC().create();
|
|
||||||
} else {
|
|
||||||
throw new FormatException("Not a supported algorithm: " + algorithm);
|
|
||||||
}
|
|
||||||
iv = Arrays.copyOfRange(ByteArrayUtils.parseHex(line.substring(ptr + 1)), 0, cipher.getIVSize());
|
|
||||||
}
|
|
||||||
} else if (line.length() > 0) {
|
|
||||||
sb.append(line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (type == null) {
|
|
||||||
throw new FormatException("PKCS5 header not found");
|
|
||||||
}
|
|
||||||
ASN1Data asn = new ASN1Data(data = decrypt(Base64.decode(sb.toString()), cipher, iv));
|
|
||||||
switch (type) {
|
|
||||||
case RSA: {
|
|
||||||
KeyFactory factory = KeyFactory.getInstance(KeyAlgorithm.RSA);
|
|
||||||
asn.readNext();
|
|
||||||
BigInteger modulus = asn.readNext();
|
|
||||||
BigInteger pubExp = asn.readNext();
|
|
||||||
BigInteger prvExp = asn.readNext();
|
|
||||||
PublicKey pubKey = factory.generatePublic(new RSAPublicKeySpec(modulus, pubExp));
|
|
||||||
PrivateKey prvKey = factory.generatePrivate(new RSAPrivateKeySpec(modulus, prvExp));
|
|
||||||
return new KeyPair(pubKey, prvKey);
|
|
||||||
}
|
|
||||||
case DSA: {
|
|
||||||
KeyFactory factory = KeyFactory.getInstance(KeyAlgorithm.DSA);
|
|
||||||
asn.readNext();
|
|
||||||
BigInteger p = asn.readNext();
|
|
||||||
BigInteger q = asn.readNext();
|
|
||||||
BigInteger g = asn.readNext();
|
|
||||||
BigInteger pub = asn.readNext();
|
|
||||||
BigInteger prv = asn.readNext();
|
|
||||||
PublicKey pubKey = factory.generatePublic(new DSAPublicKeySpec(pub, p, q, g));
|
|
||||||
PrivateKey prvKey = factory.generatePrivate(new DSAPrivateKeySpec(prv, p, q, g));
|
|
||||||
return new KeyPair(pubKey, prvKey);
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
throw new IOException("Unrecognized PKCS5 key type: " + type);
|
|
||||||
}
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new IOException(e);
|
|
||||||
} catch (InvalidKeySpecException e) {
|
|
||||||
throw new IOException(e);
|
|
||||||
} finally {
|
|
||||||
reader.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "PKCS5KeyFile{resource=" + resource + "}";
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] getPassphraseBytes() {
|
|
||||||
CharBuffer cb = CharBuffer.wrap(pwdf.reqPassword(resource));
|
|
||||||
ByteBuffer bb = IOUtils.UTF8.encode(cb);
|
|
||||||
byte[] result = Arrays.copyOfRange(bb.array(), bb.position(), bb.limit());
|
|
||||||
Arrays.fill(cb.array(), '\u0000');
|
|
||||||
Arrays.fill(bb.array(), (byte) 0);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] decrypt(byte[] raw, Cipher cipher, byte[] iv) throws DecryptException {
|
|
||||||
if (pwdf == null) {
|
|
||||||
return raw;
|
|
||||||
}
|
|
||||||
Digest md5 = new MD5();
|
|
||||||
int bsize = cipher.getBlockSize();
|
|
||||||
int hsize = md5.getBlockSize();
|
|
||||||
int hnlen = bsize / hsize * hsize + (bsize % hsize == 0 ? 0 : hsize);
|
|
||||||
do {
|
|
||||||
md5.init();
|
|
||||||
byte[] hn = new byte[hnlen];
|
|
||||||
byte[] tmp = null;
|
|
||||||
byte[] passphrase = getPassphraseBytes();
|
|
||||||
for (int i = 0; i + hsize <= hn.length; ) {
|
|
||||||
if (tmp != null) {
|
|
||||||
md5.update(tmp, 0, tmp.length);
|
|
||||||
}
|
|
||||||
md5.update(passphrase, 0, passphrase.length);
|
|
||||||
md5.update(iv, 0, iv.length > 8 ? 8 : iv.length);
|
|
||||||
tmp = md5.digest();
|
|
||||||
System.arraycopy(tmp, 0, hn, i, tmp.length);
|
|
||||||
i += tmp.length;
|
|
||||||
}
|
|
||||||
Arrays.fill(passphrase, (byte) 0);
|
|
||||||
byte[] key = Arrays.copyOfRange(hn, 0, bsize);
|
|
||||||
cipher.init(Cipher.Mode.Decrypt, key, iv);
|
|
||||||
Arrays.fill(key, (byte) 0);
|
|
||||||
byte[] decrypted = Arrays.copyOf(raw, raw.length);
|
|
||||||
cipher.update(decrypted, 0, decrypted.length);
|
|
||||||
if (ASN1Data.MAGIC == decrypted[0]) {
|
|
||||||
return decrypted;
|
|
||||||
}
|
|
||||||
} while (pwdf.shouldRetry(resource));
|
|
||||||
throw new DecryptException("Decryption failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
class ASN1Data {
|
|
||||||
static final byte MAGIC = (byte) 0x30;
|
|
||||||
|
|
||||||
private byte[] buff;
|
|
||||||
private int index, length;
|
|
||||||
|
|
||||||
ASN1Data(byte[] buff) throws FormatException {
|
|
||||||
this.buff = buff;
|
|
||||||
index = 0;
|
|
||||||
if (buff[index++] != MAGIC) {
|
|
||||||
throw new FormatException("Not ASN.1 data");
|
|
||||||
}
|
|
||||||
length = buff[index++] & 0xff;
|
|
||||||
if ((length & 0x80) != 0) {
|
|
||||||
int counter = length & 0x7f;
|
|
||||||
length = 0;
|
|
||||||
while (counter-- > 0) {
|
|
||||||
length = (length << 8) + (buff[index++] & 0xff);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ((index + length) > buff.length) {
|
|
||||||
throw new FormatException("Length mismatch: " + buff.length + " != " + (index + length));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BigInteger readNext() throws IOException {
|
|
||||||
if (index >= length) {
|
|
||||||
throw new EOFException();
|
|
||||||
} else if (buff[index++] != 0x02) {
|
|
||||||
throw new IOException("Not an int code: " + Integer.toHexString(0xff & buff[index]));
|
|
||||||
}
|
|
||||||
int length = buff[index++] & 0xff;
|
|
||||||
if ((length & 0x80) != 0) {
|
|
||||||
int counter = length & 0x7f;
|
|
||||||
length = 0;
|
|
||||||
while (counter-- > 0) {
|
|
||||||
length = (length << 8) + (buff[index++] & 0xff);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
byte[] sequence = new byte[length];
|
|
||||||
System.arraycopy(buff, index, sequence, 0, length);
|
|
||||||
index += length;
|
|
||||||
return new BigInteger(sequence);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -39,7 +39,9 @@ import org.slf4j.LoggerFactory;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
|
|
||||||
/** Represents a PKCS8-encoded key file. This is the format used by (old-style) OpenSSH and OpenSSL. */
|
/**
|
||||||
|
* Key File implementation supporting PEM-encoded PKCS8 and PKCS1 formats with or without password-based encryption
|
||||||
|
*/
|
||||||
public class PKCS8KeyFile extends BaseFileKeyProvider {
|
public class PKCS8KeyFile extends BaseFileKeyProvider {
|
||||||
|
|
||||||
public static class Factory
|
public static class Factory
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ class FileKeyProviderSpec extends Specification {
|
|||||||
|
|
||||||
where:
|
where:
|
||||||
format | keyfile
|
format | keyfile
|
||||||
KeyFormat.PKCS5 | "src/test/resources/keyformats/pkcs5"
|
KeyFormat.PKCS8 | "src/test/resources/keyformats/pkcs8"
|
||||||
KeyFormat.OpenSSH | "src/test/resources/keyformats/openssh"
|
KeyFormat.OpenSSH | "src/test/resources/keyformats/openssh"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,9 +35,9 @@ public class KeyProviderUtilTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPkcs5() throws IOException {
|
public void testPkcs1Rsa() throws IOException {
|
||||||
KeyFormat format = KeyProviderUtil.detectKeyFileFormat(new File(ROOT, "pkcs5"));
|
KeyFormat format = KeyProviderUtil.detectKeyFileFormat(new File(ROOT, "pkcs1-rsa"));
|
||||||
assertEquals(KeyFormat.PKCS5, format);
|
assertEquals(KeyFormat.PKCS8, format);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -1,91 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.keyprovider;
|
|
||||||
|
|
||||||
import net.schmizz.sshj.common.KeyType;
|
|
||||||
import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider;
|
|
||||||
import net.schmizz.sshj.userauth.keyprovider.PKCS5KeyFile;
|
|
||||||
import net.schmizz.sshj.userauth.password.PasswordFinder;
|
|
||||||
import net.schmizz.sshj.userauth.password.Resource;
|
|
||||||
import net.schmizz.sshj.util.KeyUtil;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
|
|
||||||
public class PKCS5KeyFileTest {
|
|
||||||
|
|
||||||
static final FileKeyProvider rsa = new PKCS5KeyFile();
|
|
||||||
|
|
||||||
static final String modulus = "a19f65e93926d9a2f5b52072db2c38c54e6cf0113d31fa92ff827b0f3bec609c45ea84264c88e64adba11ff093ed48ee0ed297757654b0884ab5a7e28b3c463bc9074b32837a2b69b61d914abf1d74ccd92b20fa44db3b31fb208c0dd44edaeb4ab097118e8ee374b6727b89ad6ce43f1b70c5a437ccebc36d2dad8ae973caad15cd89ae840fdae02cae42d241baef8fda8aa6bbaa54fd507a23338da6f06f61b34fb07d560e63fbce4a39c073e28573c2962cedb292b14b80d1b4e67b0465f2be0e38526232d0a7f88ce91a055fde082038a87ed91f3ef5ff971e30ea6cccf70d38498b186621c08f8fdceb8632992b480bf57fc218e91f2ca5936770fe9469";
|
|
||||||
static final String pubExp = "23";
|
|
||||||
static final String privExp = "57bcee2e2656eb2c94033d802dd62d726c6705fabad1fd0df86b67600a96431301620d395cbf5871c7af3d3974dfe5c30f5c60d95d7e6e75df69ed6c5a36a9c8aef554b5058b76a719b8478efa08ad1ebf08c8c25fe4b9bc0bfbb9be5d4f60e6213b4ab1c26ad33f5bba7d93e1cd65f65f5a79eb6ebfb32f930a2b0244378b4727acf83b5fb376c38d4abecc5dc3fc399e618e792d4c745d2dbbb9735242e5033129f2985ca3e28fa33cad2afe3e70e1b07ed2b6ec8a3f843fb4bffe3385ad211c6600618488f4ac70397e8eb036b82d811283dc728504cddbe1533c4dd31b1ec99ffa74fd0e3883a9cb3e2315cc1a56f55d38ed40520dd9ec91b4d2dd790d1b";
|
|
||||||
|
|
||||||
final char[] correctPassphrase = "passphrase".toCharArray();
|
|
||||||
final char[] incorrectPassphrase = "incorrect".toCharArray();
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp()
|
|
||||||
throws UnsupportedEncodingException, GeneralSecurityException {
|
|
||||||
rsa.init(new File("src/test/resources/id_rsa"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testKeys()
|
|
||||||
throws IOException, GeneralSecurityException {
|
|
||||||
assertEquals(KeyUtil.newRSAPublicKey(modulus, pubExp), rsa.getPublic());
|
|
||||||
assertEquals(KeyUtil.newRSAPrivateKey(modulus, privExp), rsa.getPrivate());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testType()
|
|
||||||
throws IOException {
|
|
||||||
assertEquals(rsa.getType(), KeyType.RSA);
|
|
||||||
}
|
|
||||||
|
|
||||||
final PasswordFinder givesOn3rdTry = new PasswordFinder() {
|
|
||||||
int triesLeft = 3;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public char[] reqPassword(Resource resource) {
|
|
||||||
if (triesLeft == 0)
|
|
||||||
return correctPassphrase;
|
|
||||||
else {
|
|
||||||
triesLeft--;
|
|
||||||
return incorrectPassphrase;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean shouldRetry(Resource resource) {
|
|
||||||
return triesLeft >= 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void retries()
|
|
||||||
throws IOException, GeneralSecurityException {
|
|
||||||
FileKeyProvider rsa = new PKCS5KeyFile();
|
|
||||||
rsa.init(new File("src/test/resources/rsa.pk5"), givesOn3rdTry);
|
|
||||||
assertEquals(KeyUtil.newRSAPublicKey(modulus, pubExp), rsa.getPublic());
|
|
||||||
assertEquals(KeyUtil.newRSAPrivateKey(modulus, privExp), rsa.getPrivate());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -15,110 +15,135 @@
|
|||||||
*/
|
*/
|
||||||
package net.schmizz.sshj.keyprovider;
|
package net.schmizz.sshj.keyprovider;
|
||||||
|
|
||||||
|
import com.hierynomus.sshj.common.KeyAlgorithm;
|
||||||
import com.hierynomus.sshj.common.KeyDecryptionFailedException;
|
import com.hierynomus.sshj.common.KeyDecryptionFailedException;
|
||||||
import net.schmizz.sshj.common.KeyType;
|
import net.schmizz.sshj.common.KeyType;
|
||||||
import net.schmizz.sshj.common.SecurityUtils;
|
|
||||||
import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider;
|
|
||||||
import net.schmizz.sshj.userauth.keyprovider.PKCS8KeyFile;
|
import net.schmizz.sshj.userauth.keyprovider.PKCS8KeyFile;
|
||||||
import net.schmizz.sshj.userauth.password.PasswordFinder;
|
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.util.KeyUtil;
|
import net.schmizz.sshj.util.KeyUtil;
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Rule;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.rules.ExpectedException;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.net.URL;
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.Reader;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertThrows;
|
||||||
|
|
||||||
public class PKCS8KeyFileTest {
|
public class PKCS8KeyFileTest {
|
||||||
|
|
||||||
@Rule
|
|
||||||
public ExpectedException expectedException = ExpectedException.none();
|
|
||||||
|
|
||||||
static final FileKeyProvider rsa = new PKCS8KeyFile();
|
|
||||||
|
|
||||||
static final String modulus = "a19f65e93926d9a2f5b52072db2c38c54e6cf0113d31fa92ff827b0f3bec609c45ea84264c88e64adba11ff093ed48ee0ed297757654b0884ab5a7e28b3c463bc9074b32837a2b69b61d914abf1d74ccd92b20fa44db3b31fb208c0dd44edaeb4ab097118e8ee374b6727b89ad6ce43f1b70c5a437ccebc36d2dad8ae973caad15cd89ae840fdae02cae42d241baef8fda8aa6bbaa54fd507a23338da6f06f61b34fb07d560e63fbce4a39c073e28573c2962cedb292b14b80d1b4e67b0465f2be0e38526232d0a7f88ce91a055fde082038a87ed91f3ef5ff971e30ea6cccf70d38498b186621c08f8fdceb8632992b480bf57fc218e91f2ca5936770fe9469";
|
static final String modulus = "a19f65e93926d9a2f5b52072db2c38c54e6cf0113d31fa92ff827b0f3bec609c45ea84264c88e64adba11ff093ed48ee0ed297757654b0884ab5a7e28b3c463bc9074b32837a2b69b61d914abf1d74ccd92b20fa44db3b31fb208c0dd44edaeb4ab097118e8ee374b6727b89ad6ce43f1b70c5a437ccebc36d2dad8ae973caad15cd89ae840fdae02cae42d241baef8fda8aa6bbaa54fd507a23338da6f06f61b34fb07d560e63fbce4a39c073e28573c2962cedb292b14b80d1b4e67b0465f2be0e38526232d0a7f88ce91a055fde082038a87ed91f3ef5ff971e30ea6cccf70d38498b186621c08f8fdceb8632992b480bf57fc218e91f2ca5936770fe9469";
|
||||||
static final String pubExp = "23";
|
static final String pubExp = "23";
|
||||||
static final String privExp = "57bcee2e2656eb2c94033d802dd62d726c6705fabad1fd0df86b67600a96431301620d395cbf5871c7af3d3974dfe5c30f5c60d95d7e6e75df69ed6c5a36a9c8aef554b5058b76a719b8478efa08ad1ebf08c8c25fe4b9bc0bfbb9be5d4f60e6213b4ab1c26ad33f5bba7d93e1cd65f65f5a79eb6ebfb32f930a2b0244378b4727acf83b5fb376c38d4abecc5dc3fc399e618e792d4c745d2dbbb9735242e5033129f2985ca3e28fa33cad2afe3e70e1b07ed2b6ec8a3f843fb4bffe3385ad211c6600618488f4ac70397e8eb036b82d811283dc728504cddbe1533c4dd31b1ec99ffa74fd0e3883a9cb3e2315cc1a56f55d38ed40520dd9ec91b4d2dd790d1b";
|
static final String privExp = "57bcee2e2656eb2c94033d802dd62d726c6705fabad1fd0df86b67600a96431301620d395cbf5871c7af3d3974dfe5c30f5c60d95d7e6e75df69ed6c5a36a9c8aef554b5058b76a719b8478efa08ad1ebf08c8c25fe4b9bc0bfbb9be5d4f60e6213b4ab1c26ad33f5bba7d93e1cd65f65f5a79eb6ebfb32f930a2b0244378b4727acf83b5fb376c38d4abecc5dc3fc399e618e792d4c745d2dbbb9735242e5033129f2985ca3e28fa33cad2afe3e70e1b07ed2b6ec8a3f843fb4bffe3385ad211c6600618488f4ac70397e8eb036b82d811283dc728504cddbe1533c4dd31b1ec99ffa74fd0e3883a9cb3e2315cc1a56f55d38ed40520dd9ec91b4d2dd790d1b";
|
||||||
|
static final String KEY_PASSPHRASE = "passphrase";
|
||||||
|
static final String INCORRECT_PASSPHRASE = String.class.getSimpleName();
|
||||||
|
|
||||||
@Before
|
@Test
|
||||||
public void setUp()
|
public void testKeys() throws GeneralSecurityException, IOException {
|
||||||
throws UnsupportedEncodingException, GeneralSecurityException {
|
final PKCS8KeyFile provider = new PKCS8KeyFile();
|
||||||
if (!SecurityUtils.isBouncyCastleRegistered())
|
provider.init(new File("src/test/resources/id_rsa"));
|
||||||
throw new AssertionError("bouncy castle needed");
|
assertEquals(KeyUtil.newRSAPublicKey(modulus, pubExp), provider.getPublic());
|
||||||
rsa.init(new File("src/test/resources/id_rsa"));
|
assertEquals(KeyUtil.newRSAPrivateKey(modulus, privExp), provider.getPrivate());
|
||||||
|
assertEquals(provider.getType(), KeyType.RSA);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testKeys()
|
public void testPkcs1Rsa() throws IOException {
|
||||||
throws IOException, GeneralSecurityException {
|
final PKCS8KeyFile provider = new PKCS8KeyFile();
|
||||||
assertEquals(KeyUtil.newRSAPublicKey(modulus, pubExp), rsa.getPublic());
|
provider.init(getFile("pkcs1-rsa"));
|
||||||
assertEquals(KeyUtil.newRSAPrivateKey(modulus, privExp), rsa.getPrivate());
|
assertEquals(KeyAlgorithm.RSA, provider.getPublic().getAlgorithm());
|
||||||
|
assertEquals(KeyAlgorithm.RSA, provider.getPrivate().getAlgorithm());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testType()
|
public void testPkcs1Encrypted() throws IOException, GeneralSecurityException {
|
||||||
throws IOException {
|
final PKCS8KeyFile provider = new PKCS8KeyFile();
|
||||||
assertEquals(rsa.getType(), KeyType.RSA);
|
provider.init(getFile("pkcs1-rsa-encrypted"), PasswordUtils.createOneOff(KEY_PASSPHRASE.toCharArray()));
|
||||||
|
assertEquals(KeyUtil.newRSAPublicKey(modulus, pubExp), provider.getPublic());
|
||||||
|
assertEquals(KeyUtil.newRSAPrivateKey(modulus, privExp), provider.getPrivate());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPkcs8Rsa() throws IOException {
|
public void testPkcs8Rsa() throws IOException {
|
||||||
final PKCS8KeyFile provider = new PKCS8KeyFile();
|
final PKCS8KeyFile provider = new PKCS8KeyFile();
|
||||||
provider.init(getReader("pkcs8-rsa-2048"));
|
provider.init(getFile("pkcs8-rsa-2048"));
|
||||||
assertEquals("RSA", provider.getPublic().getAlgorithm());
|
assertEquals(KeyAlgorithm.RSA, provider.getPublic().getAlgorithm());
|
||||||
assertEquals("RSA", provider.getPrivate().getAlgorithm());
|
assertEquals(KeyAlgorithm.RSA, provider.getPrivate().getAlgorithm());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPkcs8RsaEncrypted() throws IOException {
|
public void testPkcs8RsaEncrypted() throws IOException {
|
||||||
final PKCS8KeyFile provider = new PKCS8KeyFile();
|
final PKCS8KeyFile provider = new PKCS8KeyFile();
|
||||||
final PasswordFinder passwordFinder = PasswordUtils.createOneOff("passphrase".toCharArray());
|
final PasswordFinder passwordFinder = PasswordUtils.createOneOff(KEY_PASSPHRASE.toCharArray());
|
||||||
provider.init(getReader("pkcs8-rsa-2048-encrypted"), passwordFinder);
|
provider.init(getFile("pkcs8-rsa-2048-encrypted"), passwordFinder);
|
||||||
assertEquals("RSA", provider.getPublic().getAlgorithm());
|
assertEquals(KeyAlgorithm.RSA, provider.getPublic().getAlgorithm());
|
||||||
assertEquals("RSA", provider.getPrivate().getAlgorithm());
|
assertEquals(KeyAlgorithm.RSA, provider.getPrivate().getAlgorithm());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPkcs8RsaEncryptedIncorrectPassword() throws IOException {
|
public void testPkcs8RsaEncryptedIncorrectPassword() {
|
||||||
expectedException.expect(KeyDecryptionFailedException.class);
|
|
||||||
|
|
||||||
final PKCS8KeyFile provider = new PKCS8KeyFile();
|
final PKCS8KeyFile provider = new PKCS8KeyFile();
|
||||||
final PasswordFinder passwordFinder = PasswordUtils.createOneOff(String.class.getSimpleName().toCharArray());
|
final PasswordFinder passwordFinder = PasswordUtils.createOneOff(INCORRECT_PASSPHRASE.toCharArray());
|
||||||
provider.init(getReader("pkcs8-rsa-2048-encrypted"), passwordFinder);
|
provider.init(getFile("pkcs8-rsa-2048-encrypted"), passwordFinder);
|
||||||
provider.getPrivate();
|
assertThrows(KeyDecryptionFailedException.class, provider::getPrivate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPkcs8RsaEncryptedRetryPassword() throws IOException {
|
||||||
|
final PKCS8KeyFile provider = new PKCS8KeyFile();
|
||||||
|
final PasswordFinder passwordFinder = new PasswordFinder() {
|
||||||
|
private boolean retryEnabled = true;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public char[] reqPassword(Resource<?> resource) {
|
||||||
|
final char[] password;
|
||||||
|
if (retryEnabled) {
|
||||||
|
password = INCORRECT_PASSPHRASE.toCharArray();
|
||||||
|
} else {
|
||||||
|
password = KEY_PASSPHRASE.toCharArray();
|
||||||
|
}
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldRetry(Resource<?> resource) {
|
||||||
|
final boolean shouldRetry = retryEnabled;
|
||||||
|
if (retryEnabled) {
|
||||||
|
retryEnabled = false;
|
||||||
|
}
|
||||||
|
return shouldRetry;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
provider.init(getFile("pkcs8-rsa-2048-encrypted"), passwordFinder);
|
||||||
|
assertEquals(KeyAlgorithm.RSA, provider.getPublic().getAlgorithm());
|
||||||
|
assertEquals(KeyAlgorithm.RSA, provider.getPrivate().getAlgorithm());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPkcs8Ecdsa() throws IOException {
|
public void testPkcs8Ecdsa() throws IOException {
|
||||||
final PKCS8KeyFile provider = new PKCS8KeyFile();
|
final PKCS8KeyFile provider = new PKCS8KeyFile();
|
||||||
provider.init(getReader("pkcs8-ecdsa"));
|
provider.init(getFile("pkcs8-ecdsa"));
|
||||||
assertEquals("ECDSA", provider.getPublic().getAlgorithm());
|
assertEquals(KeyAlgorithm.ECDSA, provider.getPublic().getAlgorithm());
|
||||||
assertEquals("ECDSA", provider.getPrivate().getAlgorithm());
|
assertEquals(KeyAlgorithm.ECDSA, provider.getPrivate().getAlgorithm());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPkcs8Dsa() throws IOException {
|
public void testPkcs8Dsa() throws IOException {
|
||||||
final PKCS8KeyFile provider = new PKCS8KeyFile();
|
final PKCS8KeyFile provider = new PKCS8KeyFile();
|
||||||
provider.init(getReader("pkcs8-dsa"));
|
provider.init(getFile("pkcs8-dsa"));
|
||||||
assertEquals("DSA", provider.getPublic().getAlgorithm());
|
assertEquals(KeyAlgorithm.DSA, provider.getPublic().getAlgorithm());
|
||||||
assertEquals("DSA", provider.getPrivate().getAlgorithm());
|
assertEquals(KeyAlgorithm.DSA, provider.getPrivate().getAlgorithm());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Reader getReader(final String filename) {
|
private File getFile(final String filename) {
|
||||||
final String path = String.format("/keyformats/%s", filename);
|
final String path = String.format("/keyformats/%s", filename);
|
||||||
final InputStream inputStream = getClass().getResourceAsStream(path);
|
final URL resource = getClass().getResource(path);
|
||||||
if (inputStream == null) {
|
if (resource == null) {
|
||||||
throw new IllegalArgumentException(String.format("Key File [%s] not found", path));
|
throw new IllegalArgumentException(String.format("Key File [%s] not found", path));
|
||||||
}
|
}
|
||||||
return new InputStreamReader(inputStream);
|
return new File(resource.getPath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user