mirror of
https://github.com/hierynomus/sshj.git
synced 2025-12-06 07:10:53 +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.DHGexSHA256;
|
||||
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.SingletonRandomFactory;
|
||||
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.PuTTYKeyFile;
|
||||
import org.slf4j.Logger;
|
||||
@@ -162,7 +160,6 @@ public class DefaultConfig
|
||||
setFileKeyProviderFactories(
|
||||
new OpenSSHKeyV1KeyFile.Factory(),
|
||||
new PKCS8KeyFile.Factory(),
|
||||
new PKCS5KeyFile.Factory(),
|
||||
new OpenSSHKeyFile.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:
|
||||
* <ul>
|
||||
* <li>PKCS8 (OpenSSH uses this format)</li>
|
||||
* <li>PKCS5</li>
|
||||
* <li>PEM-encoded PKCS1</li>
|
||||
* <li>Putty keyfile</li>
|
||||
* <li>openssh-key-v1 (New OpenSSH keyfile format)</li>
|
||||
* </ul>
|
||||
|
||||
@@ -16,10 +16,9 @@
|
||||
package net.schmizz.sshj.userauth.keyprovider;
|
||||
|
||||
/**
|
||||
* @version $Id:$
|
||||
* Key File Formats
|
||||
*/
|
||||
public enum KeyFormat {
|
||||
PKCS5,
|
||||
PKCS8,
|
||||
OpenSSH,
|
||||
OpenSSHv1,
|
||||
|
||||
@@ -27,9 +27,9 @@ public class KeyProviderUtil {
|
||||
* <p/>
|
||||
* 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
|
||||
* @throws java.io.IOException
|
||||
* @throws java.io.IOException Thrown on file processing failures
|
||||
*/
|
||||
public static KeyFormat detectKeyFileFormat(File location)
|
||||
throws IOException {
|
||||
@@ -45,7 +45,7 @@ public class KeyProviderUtil {
|
||||
* @param privateKey Private key stored in a string
|
||||
* @param separatePubKey Is the public key stored separately from the private key
|
||||
* @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)
|
||||
throws IOException {
|
||||
@@ -60,7 +60,7 @@ public class KeyProviderUtil {
|
||||
* @param privateKey Private key accessible through a {@code Reader}
|
||||
* @param separatePubKey Is the public key stored separately from the private key
|
||||
* @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)
|
||||
throws IOException {
|
||||
@@ -94,10 +94,8 @@ public class KeyProviderUtil {
|
||||
} else if (separatePubKey) {
|
||||
// Can delay asking for password since have unencrypted pubkey
|
||||
return KeyFormat.OpenSSH;
|
||||
} else if (header.contains("BEGIN PRIVATE KEY") || header.contains("BEGIN ENCRYPTED PRIVATE KEY")) {
|
||||
return KeyFormat.PKCS8;
|
||||
} else {
|
||||
return KeyFormat.PKCS5;
|
||||
return KeyFormat.PKCS8;
|
||||
}
|
||||
} else if (header.startsWith("PuTTY-User-Key-File-")) {
|
||||
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.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 static class Factory
|
||||
|
||||
@@ -60,7 +60,7 @@ class FileKeyProviderSpec extends Specification {
|
||||
|
||||
where:
|
||||
format | keyfile
|
||||
KeyFormat.PKCS5 | "src/test/resources/keyformats/pkcs5"
|
||||
KeyFormat.PKCS8 | "src/test/resources/keyformats/pkcs8"
|
||||
KeyFormat.OpenSSH | "src/test/resources/keyformats/openssh"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,9 +35,9 @@ public class KeyProviderUtilTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPkcs5() throws IOException {
|
||||
KeyFormat format = KeyProviderUtil.detectKeyFileFormat(new File(ROOT, "pkcs5"));
|
||||
assertEquals(KeyFormat.PKCS5, format);
|
||||
public void testPkcs1Rsa() throws IOException {
|
||||
KeyFormat format = KeyProviderUtil.detectKeyFileFormat(new File(ROOT, "pkcs1-rsa"));
|
||||
assertEquals(KeyFormat.PKCS8, format);
|
||||
}
|
||||
|
||||
@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;
|
||||
|
||||
import com.hierynomus.sshj.common.KeyAlgorithm;
|
||||
import com.hierynomus.sshj.common.KeyDecryptionFailedException;
|
||||
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.password.PasswordFinder;
|
||||
import net.schmizz.sshj.userauth.password.PasswordUtils;
|
||||
import net.schmizz.sshj.userauth.password.Resource;
|
||||
import net.schmizz.sshj.util.KeyUtil;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URL;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
public class PKCS8KeyFileTest {
|
||||
|
||||
@Rule
|
||||
public ExpectedException expectedException = ExpectedException.none();
|
||||
|
||||
static final FileKeyProvider rsa = new PKCS8KeyFile();
|
||||
|
||||
static final String modulus = "a19f65e93926d9a2f5b52072db2c38c54e6cf0113d31fa92ff827b0f3bec609c45ea84264c88e64adba11ff093ed48ee0ed297757654b0884ab5a7e28b3c463bc9074b32837a2b69b61d914abf1d74ccd92b20fa44db3b31fb208c0dd44edaeb4ab097118e8ee374b6727b89ad6ce43f1b70c5a437ccebc36d2dad8ae973caad15cd89ae840fdae02cae42d241baef8fda8aa6bbaa54fd507a23338da6f06f61b34fb07d560e63fbce4a39c073e28573c2962cedb292b14b80d1b4e67b0465f2be0e38526232d0a7f88ce91a055fde082038a87ed91f3ef5ff971e30ea6cccf70d38498b186621c08f8fdceb8632992b480bf57fc218e91f2ca5936770fe9469";
|
||||
static final String pubExp = "23";
|
||||
static final String privExp = "57bcee2e2656eb2c94033d802dd62d726c6705fabad1fd0df86b67600a96431301620d395cbf5871c7af3d3974dfe5c30f5c60d95d7e6e75df69ed6c5a36a9c8aef554b5058b76a719b8478efa08ad1ebf08c8c25fe4b9bc0bfbb9be5d4f60e6213b4ab1c26ad33f5bba7d93e1cd65f65f5a79eb6ebfb32f930a2b0244378b4727acf83b5fb376c38d4abecc5dc3fc399e618e792d4c745d2dbbb9735242e5033129f2985ca3e28fa33cad2afe3e70e1b07ed2b6ec8a3f843fb4bffe3385ad211c6600618488f4ac70397e8eb036b82d811283dc728504cddbe1533c4dd31b1ec99ffa74fd0e3883a9cb3e2315cc1a56f55d38ed40520dd9ec91b4d2dd790d1b";
|
||||
static final String KEY_PASSPHRASE = "passphrase";
|
||||
static final String INCORRECT_PASSPHRASE = String.class.getSimpleName();
|
||||
|
||||
@Before
|
||||
public void setUp()
|
||||
throws UnsupportedEncodingException, GeneralSecurityException {
|
||||
if (!SecurityUtils.isBouncyCastleRegistered())
|
||||
throw new AssertionError("bouncy castle needed");
|
||||
rsa.init(new File("src/test/resources/id_rsa"));
|
||||
@Test
|
||||
public void testKeys() throws GeneralSecurityException, IOException {
|
||||
final PKCS8KeyFile provider = new PKCS8KeyFile();
|
||||
provider.init(new File("src/test/resources/id_rsa"));
|
||||
assertEquals(KeyUtil.newRSAPublicKey(modulus, pubExp), provider.getPublic());
|
||||
assertEquals(KeyUtil.newRSAPrivateKey(modulus, privExp), provider.getPrivate());
|
||||
assertEquals(provider.getType(), KeyType.RSA);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeys()
|
||||
throws IOException, GeneralSecurityException {
|
||||
assertEquals(KeyUtil.newRSAPublicKey(modulus, pubExp), rsa.getPublic());
|
||||
assertEquals(KeyUtil.newRSAPrivateKey(modulus, privExp), rsa.getPrivate());
|
||||
public void testPkcs1Rsa() throws IOException {
|
||||
final PKCS8KeyFile provider = new PKCS8KeyFile();
|
||||
provider.init(getFile("pkcs1-rsa"));
|
||||
assertEquals(KeyAlgorithm.RSA, provider.getPublic().getAlgorithm());
|
||||
assertEquals(KeyAlgorithm.RSA, provider.getPrivate().getAlgorithm());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testType()
|
||||
throws IOException {
|
||||
assertEquals(rsa.getType(), KeyType.RSA);
|
||||
public void testPkcs1Encrypted() throws IOException, GeneralSecurityException {
|
||||
final PKCS8KeyFile provider = new PKCS8KeyFile();
|
||||
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
|
||||
public void testPkcs8Rsa() throws IOException {
|
||||
final PKCS8KeyFile provider = new PKCS8KeyFile();
|
||||
provider.init(getReader("pkcs8-rsa-2048"));
|
||||
assertEquals("RSA", provider.getPublic().getAlgorithm());
|
||||
assertEquals("RSA", provider.getPrivate().getAlgorithm());
|
||||
provider.init(getFile("pkcs8-rsa-2048"));
|
||||
assertEquals(KeyAlgorithm.RSA, provider.getPublic().getAlgorithm());
|
||||
assertEquals(KeyAlgorithm.RSA, provider.getPrivate().getAlgorithm());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPkcs8RsaEncrypted() throws IOException {
|
||||
final PKCS8KeyFile provider = new PKCS8KeyFile();
|
||||
final PasswordFinder passwordFinder = PasswordUtils.createOneOff("passphrase".toCharArray());
|
||||
provider.init(getReader("pkcs8-rsa-2048-encrypted"), passwordFinder);
|
||||
assertEquals("RSA", provider.getPublic().getAlgorithm());
|
||||
assertEquals("RSA", provider.getPrivate().getAlgorithm());
|
||||
final PasswordFinder passwordFinder = PasswordUtils.createOneOff(KEY_PASSPHRASE.toCharArray());
|
||||
provider.init(getFile("pkcs8-rsa-2048-encrypted"), passwordFinder);
|
||||
assertEquals(KeyAlgorithm.RSA, provider.getPublic().getAlgorithm());
|
||||
assertEquals(KeyAlgorithm.RSA, provider.getPrivate().getAlgorithm());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPkcs8RsaEncryptedIncorrectPassword() throws IOException {
|
||||
expectedException.expect(KeyDecryptionFailedException.class);
|
||||
|
||||
public void testPkcs8RsaEncryptedIncorrectPassword() {
|
||||
final PKCS8KeyFile provider = new PKCS8KeyFile();
|
||||
final PasswordFinder passwordFinder = PasswordUtils.createOneOff(String.class.getSimpleName().toCharArray());
|
||||
provider.init(getReader("pkcs8-rsa-2048-encrypted"), passwordFinder);
|
||||
provider.getPrivate();
|
||||
final PasswordFinder passwordFinder = PasswordUtils.createOneOff(INCORRECT_PASSPHRASE.toCharArray());
|
||||
provider.init(getFile("pkcs8-rsa-2048-encrypted"), passwordFinder);
|
||||
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
|
||||
public void testPkcs8Ecdsa() throws IOException {
|
||||
final PKCS8KeyFile provider = new PKCS8KeyFile();
|
||||
provider.init(getReader("pkcs8-ecdsa"));
|
||||
assertEquals("ECDSA", provider.getPublic().getAlgorithm());
|
||||
assertEquals("ECDSA", provider.getPrivate().getAlgorithm());
|
||||
provider.init(getFile("pkcs8-ecdsa"));
|
||||
assertEquals(KeyAlgorithm.ECDSA, provider.getPublic().getAlgorithm());
|
||||
assertEquals(KeyAlgorithm.ECDSA, provider.getPrivate().getAlgorithm());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPkcs8Dsa() throws IOException {
|
||||
final PKCS8KeyFile provider = new PKCS8KeyFile();
|
||||
provider.init(getReader("pkcs8-dsa"));
|
||||
assertEquals("DSA", provider.getPublic().getAlgorithm());
|
||||
assertEquals("DSA", provider.getPrivate().getAlgorithm());
|
||||
provider.init(getFile("pkcs8-dsa"));
|
||||
assertEquals(KeyAlgorithm.DSA, provider.getPublic().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 InputStream inputStream = getClass().getResourceAsStream(path);
|
||||
if (inputStream == null) {
|
||||
final URL resource = getClass().getResource(path);
|
||||
if (resource == null) {
|
||||
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