Skip to content

Commit f52e9f0

Browse files
committed
[feat] read DH keys (not just DH parameters)
PKCS#8 and SubjectPublicKeyInfo DH keys failed to parse
1 parent b07bf83 commit f52e9f0

5 files changed

Lines changed: 97 additions & 5 deletions

File tree

src/main/java/org/jruby/ext/openssl/PKey.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
import java.security.spec.RSAKeyGenParameterSpec;
4444
import java.security.spec.X509EncodedKeySpec;
4545

46+
import javax.crypto.interfaces.DHPrivateKey;
47+
import javax.crypto.interfaces.DHPublicKey;
4648
import javax.crypto.spec.DHParameterSpec;
4749

4850
import org.bouncycastle.asn1.ASN1InputStream;
@@ -120,6 +122,9 @@ public static PKey newInstance(final Ruby runtime, final PublicKey publicKey) {
120122
if (PKeyXDH.isXDHKey(publicKey)) {
121123
return new PKeyXDH(runtime, publicKey);
122124
}
125+
if (publicKey instanceof DHPublicKey) {
126+
return new PKeyDH(runtime, (DHPublicKey) publicKey, null);
127+
}
123128
throw runtime.newNotImplementedError("public key algorithm: " + (publicKey != null ? publicKey.getAlgorithm() : "nil"));
124129
}
125130

@@ -198,6 +203,9 @@ private static IRubyObject readImpl(final ThreadContext context, IRubyObject...
198203
if ( PKeyXDH.isXDHAlgorithm(alg) ) {
199204
return PKeyXDH.newInstance(runtime, keyPair);
200205
}
206+
if ( "DH".equals(alg) ) {
207+
return new PKeyDH(runtime, (DHPublicKey) keyPair.getPublic(), (DHPrivateKey) keyPair.getPrivate());
208+
}
201209
LOG.debug(runtime, "readPrivateKey unexpected key pair algorithm: " + alg);
202210
}
203211

@@ -246,6 +254,9 @@ private static IRubyObject readImpl(final ThreadContext context, IRubyObject...
246254
if (PKeyXDH.isXDHKey(pubKey)) {
247255
return new PKeyXDH(runtime, pubKey);
248256
}
257+
if (pubKey instanceof DHPublicKey) {
258+
return new PKeyDH(runtime, (DHPublicKey) pubKey, null);
259+
}
249260

250261
// PEM_read_bio_DHparams / d2i_DHparams (last resort) - DH parameters carry no algorithm id
251262
try {

src/main/java/org/jruby/ext/openssl/PKeyDH.java

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,16 @@
3232
import java.io.StringWriter;
3333
import java.math.BigInteger;
3434
import java.security.KeyFactory;
35+
import java.security.KeyPair;
3536
import java.security.NoSuchAlgorithmException;
3637
import java.security.PrivateKey;
3738
import java.security.PublicKey;
3839
import java.util.HashMap;
3940

4041
import java.security.SecureRandom;
4142
import java.security.spec.InvalidKeySpecException;
43+
import javax.crypto.interfaces.DHPrivateKey;
44+
import javax.crypto.interfaces.DHPublicKey;
4245
import javax.crypto.spec.DHParameterSpec;
4346
import javax.crypto.spec.DHPrivateKeySpec;
4447
import javax.crypto.spec.DHPublicKeySpec;
@@ -117,6 +120,11 @@ public PKeyDH(Ruby runtime, RubyClass clazz) {
117120
this.dh_g = spec.getG();
118121
}
119122

123+
PKeyDH(Ruby runtime, javax.crypto.interfaces.DHPublicKey publicKey, javax.crypto.interfaces.DHPrivateKey privateKey) {
124+
this(runtime);
125+
initKey(publicKey, privateKey);
126+
}
127+
120128
@Override
121129
@JRubyMethod
122130
public RubyString oid() {
@@ -192,11 +200,17 @@ public synchronized IRubyObject initialize(final ThreadContext context, final IR
192200
try {
193201
DHParameterSpec spec = PEMInputOutput.readDHParameters(new StringReader(str.toString()));
194202
if (spec == null) {
195-
spec = org.jruby.ext.openssl.impl.PKey.readDHParameter(str.getByteList().bytes());
196-
}
197-
if (spec == null) {
198-
throw runtime.newArgumentError("invalid DH PARAMETERS");
203+
try {
204+
spec = org.jruby.ext.openssl.impl.PKey.readDHParameter(str.getByteList().bytes());
205+
}
206+
catch (IOException e) { // not parameters - a PKCS#8 or SubjectPublicKeyInfo DH key then
207+
if (!initFromKey(context, str)) throw e;
208+
return this;
209+
}
199210
}
211+
212+
if (spec == null) throw runtime.newArgumentError("invalid DH PARAMETERS");
213+
200214
this.dh_p = spec.getP();
201215
this.dh_g = spec.getG();
202216
}
@@ -213,6 +227,35 @@ public synchronized IRubyObject initialize(final ThreadContext context, final IR
213227
return this;
214228
}
215229

230+
// a DH key (not just parameters) is a plain PKCS#8 PrivateKeyInfo or SubjectPublicKeyInfo
231+
private boolean initFromKey(final ThreadContext context, final RubyString str) {
232+
try {
233+
KeyPair keyPair = PKey.readPrivateKey(str, null);
234+
if (keyPair != null && keyPair.getPrivate() instanceof DHPrivateKey) {
235+
initKey((DHPublicKey) keyPair.getPublic(), (DHPrivateKey) keyPair.getPrivate());
236+
return true;
237+
}
238+
}
239+
catch (Exception e) { /* not a DH private key */ }
240+
try {
241+
PublicKey pubKey = org.jruby.ext.openssl.impl.PKey.readPublicKey(StringHelper.readX509PEM(context, str));
242+
if (pubKey instanceof DHPublicKey) {
243+
initKey((DHPublicKey) pubKey, null);
244+
return true;
245+
}
246+
}
247+
catch (Exception e) { /* not a DH public key */ }
248+
return false;
249+
}
250+
251+
private void initKey(DHPublicKey publicKey, DHPrivateKey privateKey) {
252+
final DHParameterSpec params = privateKey != null ? privateKey.getParams() : publicKey.getParams();
253+
this.dh_p = params.getP();
254+
this.dh_g = params.getG();
255+
if (privateKey != null) this.dh_x = privateKey.getX();
256+
if (publicKey != null) this.dh_y = publicKey.getY();
257+
}
258+
216259
private void generate(final Ruby runtime, final IRubyObject bits, final int gval) {
217260
BigInteger p;
218261
try {

src/main/java/org/jruby/ext/openssl/impl/PKey.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@
5252
import java.security.spec.RSAPrivateCrtKeySpec;
5353
import java.security.spec.RSAPublicKeySpec;
5454
import java.security.spec.X509EncodedKeySpec;
55+
import javax.crypto.interfaces.DHPrivateKey;
5556
import javax.crypto.spec.DHParameterSpec;
57+
import javax.crypto.spec.DHPublicKeySpec;
5658

5759
import org.bouncycastle.asn1.ASN1Encodable;
5860
import org.bouncycastle.asn1.ASN1EncodableVector;
@@ -88,7 +90,7 @@
8890
*/
8991
public class PKey {
9092

91-
public enum Type { RSA, DSA, EC, EdDSA, XDH; }
93+
public enum Type { RSA, DSA, EC, EdDSA, XDH, DH; }
9294

9395
public static KeyPair readPrivateKey(final Type type, final byte[] input)
9496
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
@@ -156,6 +158,8 @@ public static KeyPair readPrivateKey(final Type type, final PrivateKeyInfo keyIn
156158
return readEdDSAPrivateKey(keyInfo);
157159
case XDH:
158160
return readXDHPrivateKey(keyInfo);
161+
case DH:
162+
return readDHPrivateKey(keyInfo);
159163
default:
160164
throw new AssertionError("unexpected key type: " + type);
161165
}
@@ -206,6 +210,7 @@ private static Type matchPublicKeyType(final AlgorithmIdentifier algId) throws I
206210
if (EdECObjectIdentifiers.id_Ed448.equals(algIdentifier)) return Type.EdDSA;
207211
if (EdECObjectIdentifiers.id_X25519.equals(algIdentifier)) return Type.XDH;
208212
if (EdECObjectIdentifiers.id_X448.equals(algIdentifier)) return Type.XDH;
213+
if (PKCSObjectIdentifiers.dhKeyAgreement.equals(algIdentifier)) return Type.DH;
209214

210215
throw new IOException("unsupported public key algorithm: " + algIdentifier);
211216
}
@@ -339,6 +344,17 @@ public static DHParameterSpec readDHParameter(final byte[] input) throws IOExcep
339344
return new DHParameterSpec(p, g);
340345
}
341346

347+
// PKCS#8 DH - only the private value is stored, the public one gets derived
348+
private static KeyPair readDHPrivateKey(final PrivateKeyInfo keyInfo)
349+
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
350+
KeyFactory keyFactory = SecurityHelper.getKeyFactory("DH");
351+
DHPrivateKey privateKey = (DHPrivateKey) keyFactory.generatePrivate(new PKCS8EncodedKeySpec(keyInfo.getEncoded()));
352+
DHParameterSpec params = privateKey.getParams();
353+
BigInteger y = params.getG().modPow(privateKey.getX(), params.getP());
354+
PublicKey publicKey = keyFactory.generatePublic(new DHPublicKeySpec(y, params.getP(), params.getG()));
355+
return new KeyPair(publicKey, privateKey);
356+
}
357+
342358
// XDH (X25519 / X448)
343359
private static KeyPair readXDHPrivateKey(final PrivateKeyInfo keyInfo)
344360
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {

src/main/java/org/jruby/ext/openssl/x509store/PEMInputOutput.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1478,6 +1478,9 @@ private static Type getPrivateKeyType(final AlgorithmIdentifier algId) {
14781478
org.bouncycastle.asn1.edec.EdECObjectIdentifiers.id_X448.equals(algIdentifier)) {
14791479
return Type.XDH;
14801480
}
1481+
if (PKCSObjectIdentifiers.dhKeyAgreement.equals(algIdentifier)) {
1482+
return Type.DH;
1483+
}
14811484

14821485
return Type.valueOf(algIdentifier.getId());
14831486
}

test/test_pkey_dh.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,25 @@ def test_pkey_read_parameters
5858
end
5959
end
6060

61+
# a DH key (rather than bare parameters) is a plain PKCS#8 / SubjectPublicKeyInfo
62+
def test_pkey_read_key
63+
key = OpenSSL::PKey.generate_key(Fixtures.pkey_dh('dh2048_ffdhe2048'))
64+
65+
priv = OpenSSL::PKey.read(key.private_to_pem)
66+
assert_kind_of OpenSSL::PKey::DH, priv
67+
assert_equal key.priv_key, priv.priv_key
68+
assert_equal key.pub_key, priv.pub_key # not stored in PKCS#8, gets derived
69+
assert_equal key.p, priv.p
70+
71+
pub = OpenSSL::PKey.read(key.public_to_pem)
72+
assert_kind_of OpenSSL::PKey::DH, pub
73+
assert_nil pub.priv_key
74+
assert_equal key.pub_key, pub.pub_key
75+
76+
assert_equal key.priv_key, OpenSSL::PKey::DH.new(key.private_to_pem).priv_key
77+
assert_equal key.pub_key, OpenSSL::PKey::DH.new(key.public_to_pem).pub_key
78+
end
79+
6180
# DH parameters are a SEQUENCE of INTEGERs, which an RSA (9) or DSA (6)
6281
# private key also starts with - those must never be taken for DH parameters
6382
def test_pkey_read_der_private_key_is_not_dh

0 commit comments

Comments
 (0)