mirror of
https://github.com/hierynomus/sshj.git
synced 2025-12-06 23:30:55 +03:00
Update OpenSSH Key V1 parsing using CRT information for RSA Private Keys (#726)
* Update OpenSSH Key V1 parsing using CRT information for RSA Private Keys * Remove unndeeded BC call. Signed-off-by: Jeroen van Erp <jeroen@hierynomus.com> Co-authored-by: Jeroen van Erp <jeroen@hierynomus.com>
This commit is contained in:
@@ -45,7 +45,7 @@ import java.nio.CharBuffer;
|
|||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.security.*;
|
import java.security.*;
|
||||||
import java.security.spec.ECPrivateKeySpec;
|
import java.security.spec.ECPrivateKeySpec;
|
||||||
import java.security.spec.RSAPrivateKeySpec;
|
import java.security.spec.RSAPrivateCrtKeySpec;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -245,13 +245,9 @@ public class OpenSSHKeyV1KeyFile extends BaseFileKeyProvider {
|
|||||||
kp = new KeyPair(publicKey, new EdDSAPrivateKey(new EdDSAPrivateKeySpec(privKey, EdDSANamedCurveTable.getByName("Ed25519"))));
|
kp = new KeyPair(publicKey, new EdDSAPrivateKey(new EdDSAPrivateKeySpec(privKey, EdDSANamedCurveTable.getByName("Ed25519"))));
|
||||||
break;
|
break;
|
||||||
case RSA:
|
case RSA:
|
||||||
BigInteger n = keyBuffer.readMPInt(); // Modulus
|
final RSAPrivateCrtKeySpec rsaPrivateCrtKeySpec = readRsaPrivateKeySpec(keyBuffer);
|
||||||
keyBuffer.readMPInt(); // Public Exponent
|
final PrivateKey privateKey = SecurityUtils.getKeyFactory(KeyAlgorithm.RSA).generatePrivate(rsaPrivateCrtKeySpec);
|
||||||
BigInteger d = keyBuffer.readMPInt(); // Private Exponent
|
kp = new KeyPair(publicKey, privateKey);
|
||||||
keyBuffer.readMPInt(); // iqmp (q^-1 mod p)
|
|
||||||
keyBuffer.readMPInt(); // p (Prime 1)
|
|
||||||
keyBuffer.readMPInt(); // q (Prime 2)
|
|
||||||
kp = new KeyPair(publicKey, SecurityUtils.getKeyFactory(KeyAlgorithm.RSA).generatePrivate(new RSAPrivateKeySpec(n, d)));
|
|
||||||
break;
|
break;
|
||||||
case ECDSA256:
|
case ECDSA256:
|
||||||
kp = new KeyPair(publicKey, createECDSAPrivateKey(kt, keyBuffer, "P-256"));
|
kp = new KeyPair(publicKey, createECDSAPrivateKey(kt, keyBuffer, "P-256"));
|
||||||
@@ -284,6 +280,35 @@ public class OpenSSHKeyV1KeyFile extends BaseFileKeyProvider {
|
|||||||
ECNamedCurveSpec ecCurveSpec = new ECNamedCurveSpec(name, ecParams.getCurve(), ecParams.getG(), ecParams.getN());
|
ECNamedCurveSpec ecCurveSpec = new ECNamedCurveSpec(name, ecParams.getCurve(), ecParams.getG(), ecParams.getN());
|
||||||
ECPrivateKeySpec pks = new ECPrivateKeySpec(s, ecCurveSpec);
|
ECPrivateKeySpec pks = new ECPrivateKeySpec(s, ecCurveSpec);
|
||||||
return SecurityUtils.getKeyFactory(KeyAlgorithm.ECDSA).generatePrivate(pks);
|
return SecurityUtils.getKeyFactory(KeyAlgorithm.ECDSA).generatePrivate(pks);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read RSA Private CRT Key Spec according to OpenSSH sshkey_private_deserialize in sshkey.c
|
||||||
|
*
|
||||||
|
* @param buffer Buffer
|
||||||
|
* @return RSA Private CRT Key Specification
|
||||||
|
* @throws Buffer.BufferException Thrown on failure to read from buffer
|
||||||
|
*/
|
||||||
|
private RSAPrivateCrtKeySpec readRsaPrivateKeySpec(final PlainBuffer buffer) throws Buffer.BufferException {
|
||||||
|
final BigInteger modulus = buffer.readMPInt();
|
||||||
|
final BigInteger publicExponent = buffer.readMPInt();
|
||||||
|
final BigInteger privateExponent = buffer.readMPInt();
|
||||||
|
final BigInteger crtCoefficient = buffer.readMPInt(); // iqmp (q^-1 mod p)
|
||||||
|
final BigInteger primeP = buffer.readMPInt();
|
||||||
|
final BigInteger primeQ = buffer.readMPInt();
|
||||||
|
|
||||||
|
// Calculate Prime Exponent P and Prime Exponent Q according to RFC 8017 Section 3.2
|
||||||
|
final BigInteger primeExponentP = privateExponent.remainder(primeP.subtract(BigInteger.ONE));
|
||||||
|
final BigInteger primeExponentQ = privateExponent.remainder(primeQ.subtract(BigInteger.ONE));
|
||||||
|
return new RSAPrivateCrtKeySpec(
|
||||||
|
modulus,
|
||||||
|
publicExponent,
|
||||||
|
privateExponent,
|
||||||
|
primeP,
|
||||||
|
primeQ,
|
||||||
|
primeExponentP,
|
||||||
|
primeExponentQ,
|
||||||
|
crtCoefficient
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import java.math.BigInteger;
|
|||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
|
import java.security.interfaces.RSAPrivateCrtKey;
|
||||||
import java.security.interfaces.RSAPublicKey;
|
import java.security.interfaces.RSAPublicKey;
|
||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
@@ -103,7 +104,7 @@ public class OpenSSHKeyFileTest {
|
|||||||
assertArrayEquals(new char[passwordChars.length], passwordChars);
|
assertArrayEquals(new char[passwordChars.length], passwordChars);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
final PasswordFinder onlyGivesWhenReady = new PasswordFinder() {
|
final PasswordFinder onlyGivesWhenReady = new PasswordFinder() {
|
||||||
@Override
|
@Override
|
||||||
@@ -187,7 +188,7 @@ public class OpenSSHKeyFileTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldHaveCorrectFingerprintForECDSA256() throws IOException, GeneralSecurityException {
|
public void shouldHaveCorrectFingerprintForECDSA256() throws IOException {
|
||||||
OpenSSHKeyFile keyFile = new OpenSSHKeyFile();
|
OpenSSHKeyFile keyFile = new OpenSSHKeyFile();
|
||||||
keyFile.init(new File("src/test/resources/keytypes/test_ecdsa_nistp256"));
|
keyFile.init(new File("src/test/resources/keytypes/test_ecdsa_nistp256"));
|
||||||
String expected = "256 MD5:53:ae:db:ed:8f:2d:02:d4:d5:6c:24:bc:a4:66:88:79 root@itgcpkerberosstack-cbgateway-0-20151117031915 (ECDSA)\n";
|
String expected = "256 MD5:53:ae:db:ed:8f:2d:02:d4:d5:6c:24:bc:a4:66:88:79 root@itgcpkerberosstack-cbgateway-0-20151117031915 (ECDSA)\n";
|
||||||
@@ -197,7 +198,7 @@ public class OpenSSHKeyFileTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldHaveCorrectFingerprintForECDSA384() throws IOException, GeneralSecurityException {
|
public void shouldHaveCorrectFingerprintForECDSA384() throws IOException {
|
||||||
OpenSSHKeyFile keyFile = new OpenSSHKeyFile();
|
OpenSSHKeyFile keyFile = new OpenSSHKeyFile();
|
||||||
keyFile.init(new File("src/test/resources/keytypes/test_ecdsa_nistp384"));
|
keyFile.init(new File("src/test/resources/keytypes/test_ecdsa_nistp384"));
|
||||||
String expected = "384 MD5:ee:9b:82:d1:47:01:16:1b:27:da:f5:27:fd:b2:eb:e2";
|
String expected = "384 MD5:ee:9b:82:d1:47:01:16:1b:27:da:f5:27:fd:b2:eb:e2";
|
||||||
@@ -207,7 +208,7 @@ public class OpenSSHKeyFileTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldHaveCorrectFingerprintForECDSA521() throws IOException, GeneralSecurityException {
|
public void shouldHaveCorrectFingerprintForECDSA521() throws IOException {
|
||||||
OpenSSHKeyFile keyFile = new OpenSSHKeyFile();
|
OpenSSHKeyFile keyFile = new OpenSSHKeyFile();
|
||||||
keyFile.init(new File("src/test/resources/keytypes/test_ecdsa_nistp521"));
|
keyFile.init(new File("src/test/resources/keytypes/test_ecdsa_nistp521"));
|
||||||
String expected = "521 MD5:22:e2:f4:3c:61:ae:e9:85:a1:4d:d9:6c:13:aa:eb:00";
|
String expected = "521 MD5:22:e2:f4:3c:61:ae:e9:85:a1:4d:d9:6c:13:aa:eb:00";
|
||||||
@@ -274,6 +275,35 @@ public class OpenSSHKeyFileTest {
|
|||||||
assertThat(aPrivate.getAlgorithm(), equalTo("RSA"));
|
assertThat(aPrivate.getAlgorithm(), equalTo("RSA"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldLoadRSAPrivateCrtKeyAsOpenSSHV1() throws IOException {
|
||||||
|
final OpenSSHKeyV1KeyFile keyFile = new OpenSSHKeyV1KeyFile();
|
||||||
|
keyFile.init(new File("src/test/resources/keyformats/rsa_opensshv1"));
|
||||||
|
final PrivateKey privateKey = keyFile.getPrivate();
|
||||||
|
final PublicKey publicKey = keyFile.getPublic();
|
||||||
|
|
||||||
|
assertTrue(publicKey instanceof RSAPublicKey);
|
||||||
|
final RSAPublicKey rsaPublicKey = (RSAPublicKey) publicKey;
|
||||||
|
|
||||||
|
assertTrue(privateKey instanceof RSAPrivateCrtKey);
|
||||||
|
final RSAPrivateCrtKey rsaPrivateCrtKey = (RSAPrivateCrtKey) privateKey;
|
||||||
|
|
||||||
|
assertEquals("Public Key Exponent not matched", rsaPublicKey.getPublicExponent(), rsaPrivateCrtKey.getPublicExponent());
|
||||||
|
assertEquals("Public Key Modulus not matched", rsaPublicKey.getModulus(), rsaPrivateCrtKey.getModulus());
|
||||||
|
|
||||||
|
final BigInteger privateExponent = rsaPrivateCrtKey.getPrivateExponent();
|
||||||
|
|
||||||
|
final BigInteger expectedPrimeExponentP = privateExponent.mod(rsaPrivateCrtKey.getPrimeP().subtract(BigInteger.ONE));
|
||||||
|
assertEquals("Prime Exponent P not matched", expectedPrimeExponentP, rsaPrivateCrtKey.getPrimeExponentP());
|
||||||
|
|
||||||
|
final BigInteger expectedPrimeExponentQ = privateExponent.mod(rsaPrivateCrtKey.getPrimeQ().subtract(BigInteger.ONE));
|
||||||
|
assertEquals("Prime Exponent Q not matched", expectedPrimeExponentQ, rsaPrivateCrtKey.getPrimeExponentQ());
|
||||||
|
|
||||||
|
|
||||||
|
final BigInteger expectedCoefficient = rsaPrivateCrtKey.getPrimeQ().modInverse(rsaPrivateCrtKey.getPrimeP());
|
||||||
|
assertEquals("Prime CRT Coefficient not matched", expectedCoefficient, rsaPrivateCrtKey.getCrtCoefficient());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldLoadECDSAPrivateKeyAsOpenSSHV1() throws IOException {
|
public void shouldLoadECDSAPrivateKeyAsOpenSSHV1() throws IOException {
|
||||||
OpenSSHKeyV1KeyFile keyFile = new OpenSSHKeyV1KeyFile();
|
OpenSSHKeyV1KeyFile keyFile = new OpenSSHKeyV1KeyFile();
|
||||||
|
|||||||
Reference in New Issue
Block a user