Skip to content

Commit 03dd1aa

Browse files
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 <[email protected]> Co-authored-by: Jeroen van Erp <[email protected]>
1 parent 7742d9b commit 03dd1aa

File tree

2 files changed

+67
-12
lines changed

2 files changed

+67
-12
lines changed

src/main/java/com/hierynomus/sshj/userauth/keyprovider/OpenSSHKeyV1KeyFile.java

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
import java.nio.charset.Charset;
4646
import java.security.*;
4747
import java.security.spec.ECPrivateKeySpec;
48-
import java.security.spec.RSAPrivateKeySpec;
48+
import java.security.spec.RSAPrivateCrtKeySpec;
4949
import java.util.Arrays;
5050

5151
/**
@@ -245,13 +245,9 @@ private KeyPair readUnencrypted(final PlainBuffer keyBuffer, final PublicKey pub
245245
kp = new KeyPair(publicKey, new EdDSAPrivateKey(new EdDSAPrivateKeySpec(privKey, EdDSANamedCurveTable.getByName("Ed25519"))));
246246
break;
247247
case RSA:
248-
BigInteger n = keyBuffer.readMPInt(); // Modulus
249-
keyBuffer.readMPInt(); // Public Exponent
250-
BigInteger d = keyBuffer.readMPInt(); // Private Exponent
251-
keyBuffer.readMPInt(); // iqmp (q^-1 mod p)
252-
keyBuffer.readMPInt(); // p (Prime 1)
253-
keyBuffer.readMPInt(); // q (Prime 2)
254-
kp = new KeyPair(publicKey, SecurityUtils.getKeyFactory(KeyAlgorithm.RSA).generatePrivate(new RSAPrivateKeySpec(n, d)));
248+
final RSAPrivateCrtKeySpec rsaPrivateCrtKeySpec = readRsaPrivateKeySpec(keyBuffer);
249+
final PrivateKey privateKey = SecurityUtils.getKeyFactory(KeyAlgorithm.RSA).generatePrivate(rsaPrivateCrtKeySpec);
250+
kp = new KeyPair(publicKey, privateKey);
255251
break;
256252
case ECDSA256:
257253
kp = new KeyPair(publicKey, createECDSAPrivateKey(kt, keyBuffer, "P-256"));
@@ -284,6 +280,35 @@ private PrivateKey createECDSAPrivateKey(KeyType kt, PlainBuffer buffer, String
284280
ECNamedCurveSpec ecCurveSpec = new ECNamedCurveSpec(name, ecParams.getCurve(), ecParams.getG(), ecParams.getN());
285281
ECPrivateKeySpec pks = new ECPrivateKeySpec(s, ecCurveSpec);
286282
return SecurityUtils.getKeyFactory(KeyAlgorithm.ECDSA).generatePrivate(pks);
283+
}
284+
285+
/**
286+
* Read RSA Private CRT Key Spec according to OpenSSH sshkey_private_deserialize in sshkey.c
287+
*
288+
* @param buffer Buffer
289+
* @return RSA Private CRT Key Specification
290+
* @throws Buffer.BufferException Thrown on failure to read from buffer
291+
*/
292+
private RSAPrivateCrtKeySpec readRsaPrivateKeySpec(final PlainBuffer buffer) throws Buffer.BufferException {
293+
final BigInteger modulus = buffer.readMPInt();
294+
final BigInteger publicExponent = buffer.readMPInt();
295+
final BigInteger privateExponent = buffer.readMPInt();
296+
final BigInteger crtCoefficient = buffer.readMPInt(); // iqmp (q^-1 mod p)
297+
final BigInteger primeP = buffer.readMPInt();
298+
final BigInteger primeQ = buffer.readMPInt();
287299

300+
// Calculate Prime Exponent P and Prime Exponent Q according to RFC 8017 Section 3.2
301+
final BigInteger primeExponentP = privateExponent.remainder(primeP.subtract(BigInteger.ONE));
302+
final BigInteger primeExponentQ = privateExponent.remainder(primeQ.subtract(BigInteger.ONE));
303+
return new RSAPrivateCrtKeySpec(
304+
modulus,
305+
publicExponent,
306+
privateExponent,
307+
primeP,
308+
primeQ,
309+
primeExponentP,
310+
primeExponentQ,
311+
crtCoefficient
312+
);
288313
}
289314
}

src/test/java/net/schmizz/sshj/keyprovider/OpenSSHKeyFileTest.java

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import java.security.GeneralSecurityException;
4444
import java.security.PrivateKey;
4545
import java.security.PublicKey;
46+
import java.security.interfaces.RSAPrivateCrtKey;
4647
import java.security.interfaces.RSAPublicKey;
4748
import java.text.DateFormat;
4849
import java.text.ParseException;
@@ -103,7 +104,7 @@ public void assertWiped() {
103104
assertArrayEquals(new char[passwordChars.length], passwordChars);
104105
}
105106
}
106-
};
107+
}
107108

108109
final PasswordFinder onlyGivesWhenReady = new PasswordFinder() {
109110
@Override
@@ -187,7 +188,7 @@ public void fromString()
187188
}
188189

189190
@Test
190-
public void shouldHaveCorrectFingerprintForECDSA256() throws IOException, GeneralSecurityException {
191+
public void shouldHaveCorrectFingerprintForECDSA256() throws IOException {
191192
OpenSSHKeyFile keyFile = new OpenSSHKeyFile();
192193
keyFile.init(new File("src/test/resources/keytypes/test_ecdsa_nistp256"));
193194
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 void shouldHaveCorrectFingerprintForECDSA256() throws IOException, Genera
197198
}
198199

199200
@Test
200-
public void shouldHaveCorrectFingerprintForECDSA384() throws IOException, GeneralSecurityException {
201+
public void shouldHaveCorrectFingerprintForECDSA384() throws IOException {
201202
OpenSSHKeyFile keyFile = new OpenSSHKeyFile();
202203
keyFile.init(new File("src/test/resources/keytypes/test_ecdsa_nistp384"));
203204
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 void shouldHaveCorrectFingerprintForECDSA384() throws IOException, Genera
207208
}
208209

209210
@Test
210-
public void shouldHaveCorrectFingerprintForECDSA521() throws IOException, GeneralSecurityException {
211+
public void shouldHaveCorrectFingerprintForECDSA521() throws IOException {
211212
OpenSSHKeyFile keyFile = new OpenSSHKeyFile();
212213
keyFile.init(new File("src/test/resources/keytypes/test_ecdsa_nistp521"));
213214
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 void shouldLoadRSAPrivateKeyAsOpenSSHV1() throws IOException {
274275
assertThat(aPrivate.getAlgorithm(), equalTo("RSA"));
275276
}
276277

278+
@Test
279+
public void shouldLoadRSAPrivateCrtKeyAsOpenSSHV1() throws IOException {
280+
final OpenSSHKeyV1KeyFile keyFile = new OpenSSHKeyV1KeyFile();
281+
keyFile.init(new File("src/test/resources/keyformats/rsa_opensshv1"));
282+
final PrivateKey privateKey = keyFile.getPrivate();
283+
final PublicKey publicKey = keyFile.getPublic();
284+
285+
assertTrue(publicKey instanceof RSAPublicKey);
286+
final RSAPublicKey rsaPublicKey = (RSAPublicKey) publicKey;
287+
288+
assertTrue(privateKey instanceof RSAPrivateCrtKey);
289+
final RSAPrivateCrtKey rsaPrivateCrtKey = (RSAPrivateCrtKey) privateKey;
290+
291+
assertEquals("Public Key Exponent not matched", rsaPublicKey.getPublicExponent(), rsaPrivateCrtKey.getPublicExponent());
292+
assertEquals("Public Key Modulus not matched", rsaPublicKey.getModulus(), rsaPrivateCrtKey.getModulus());
293+
294+
final BigInteger privateExponent = rsaPrivateCrtKey.getPrivateExponent();
295+
296+
final BigInteger expectedPrimeExponentP = privateExponent.mod(rsaPrivateCrtKey.getPrimeP().subtract(BigInteger.ONE));
297+
assertEquals("Prime Exponent P not matched", expectedPrimeExponentP, rsaPrivateCrtKey.getPrimeExponentP());
298+
299+
final BigInteger expectedPrimeExponentQ = privateExponent.mod(rsaPrivateCrtKey.getPrimeQ().subtract(BigInteger.ONE));
300+
assertEquals("Prime Exponent Q not matched", expectedPrimeExponentQ, rsaPrivateCrtKey.getPrimeExponentQ());
301+
302+
303+
final BigInteger expectedCoefficient = rsaPrivateCrtKey.getPrimeQ().modInverse(rsaPrivateCrtKey.getPrimeP());
304+
assertEquals("Prime CRT Coefficient not matched", expectedCoefficient, rsaPrivateCrtKey.getCrtCoefficient());
305+
}
306+
277307
@Test
278308
public void shouldLoadECDSAPrivateKeyAsOpenSSHV1() throws IOException {
279309
OpenSSHKeyV1KeyFile keyFile = new OpenSSHKeyV1KeyFile();

0 commit comments

Comments
 (0)