Skip to content

Commit 7dbac3c

Browse files
committed
[compat] implement PKey::RSA public_to_der and public_to_pem
1 parent 888eb19 commit 7dbac3c

File tree

3 files changed

+136
-24
lines changed

3 files changed

+136
-24
lines changed

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

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
import static org.jruby.ext.openssl.impl.PKey.readRSAPublicKey;
7878
import static org.jruby.ext.openssl.impl.PKey.toASN1Primitive;
7979
import static org.jruby.ext.openssl.impl.PKey.toDerRSAKey;
80+
import static org.jruby.ext.openssl.impl.PKey.toDerRSAPublicKey;
8081

8182
/**
8283
* @author <a href="mailto:[email protected]">Ola Bini</a>
@@ -356,6 +357,21 @@ public RubyBoolean private_p() {
356357
return getRuntime().newBoolean(isPrivateKey());
357358
}
358359

360+
@JRubyMethod(name = "public_to_der")
361+
public RubyString public_to_der(ThreadContext context) {
362+
final byte[] bytes;
363+
try {
364+
bytes = toDerRSAPublicKey(publicKey);
365+
}
366+
catch (NoClassDefFoundError e) {
367+
throw newRSAError(context.runtime, bcExceptionMessage(e));
368+
}
369+
catch (Exception e) {
370+
throw newRSAError(getRuntime(), e.getMessage(), e);
371+
}
372+
return StringHelper.newString(context.runtime, bytes);
373+
}
374+
359375
@Override
360376
@JRubyMethod(name = "to_der")
361377
public RubyString to_der() {
@@ -366,8 +382,8 @@ public RubyString to_der() {
366382
catch (NoClassDefFoundError e) {
367383
throw newRSAError(getRuntime(), bcExceptionMessage(e));
368384
}
369-
catch (IOException e) {
370-
throw newRSAError(getRuntime(), e.getMessage());
385+
catch (Exception e) {
386+
throw newRSAError(getRuntime(), e.getMessage(), e);
371387
}
372388
return StringHelper.newString(getRuntime(), bytes);
373389
}
@@ -470,6 +486,21 @@ public RubyString to_pem(ThreadContext context, final IRubyObject[] args) {
470486
}
471487
}
472488

489+
@JRubyMethod
490+
public RubyString public_to_pem(ThreadContext context) {
491+
try {
492+
final StringWriter writer = new StringWriter();
493+
PEMInputOutput.writeRSAPublicKey(writer, publicKey);
494+
return RubyString.newString(context.runtime, writer.getBuffer());
495+
}
496+
catch (NoClassDefFoundError ncdfe) {
497+
throw newRSAError(context.runtime, bcExceptionMessage(ncdfe));
498+
}
499+
catch (IOException ioe) {
500+
throw newRSAError(context.runtime, ioe.getMessage());
501+
}
502+
}
503+
473504
private String getPadding(final int padding) {
474505
if ( padding < 1 || padding > 4 ) {
475506
throw newRSAError(getRuntime(), "");
@@ -745,6 +776,7 @@ public IRubyObject set_key(final ThreadContext context, IRubyObject n, IRubyObje
745776
this.rsa_n = BN.getBigInteger(n);
746777
this.rsa_e = BN.getBigInteger(e);
747778
this.rsa_d = BN.getBigInteger(d);
779+
generatePublicKeyIfParams(context);
748780
generatePrivateKeyIfParams(context);
749781
return this;
750782
}
@@ -769,8 +801,6 @@ public IRubyObject set_crt_params(final ThreadContext context, IRubyObject dmp1,
769801
private void generatePublicKeyIfParams(final ThreadContext context) {
770802
final Ruby runtime = context.runtime;
771803

772-
if ( publicKey != null ) throw newRSAError(runtime, "illegal modification");
773-
774804
// Don't access the rsa_n and rsa_e fields directly. They may have
775805
// already been consumed and cleared by generatePrivateKeyIfParams.
776806
BigInteger _rsa_n = getModulus();

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

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ public static KeyPair readPrivateKey(final Type type, final PrivateKeyInfo keyIn
138138
}
139139

140140
// d2i_PUBKEY_bio
141-
public static PublicKey readPublicKey(byte[] input) throws IOException {
141+
public static PublicKey readPublicKey(final byte[] input) throws IOException {
142142
try (Reader in = new InputStreamReader(new ByteArrayInputStream(input))) {
143143
Object pemObject = new PEMParser(in).readObject();
144144
SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfo.getInstance(pemObject);
@@ -292,10 +292,7 @@ public static KeyPair readECPrivateKey(final KeyFactory keyFactory, final Privat
292292

293293
public static byte[] toDerRSAKey(RSAPublicKey pubKey, RSAPrivateCrtKey privKey) throws IOException {
294294
if ( pubKey != null && privKey == null ) {
295-
// pubKey.getEncoded() :
296-
return KeyUtil.getEncodedSubjectPublicKeyInfo(
297-
new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE), toASN1Primitive(pubKey)
298-
);
295+
return toDerRSAPublicKey(pubKey);
299296
}
300297
ASN1EncodableVector vec = new ASN1EncodableVector();
301298
vec.add(new ASN1Integer(BigInteger.ZERO));
@@ -310,6 +307,13 @@ public static byte[] toDerRSAKey(RSAPublicKey pubKey, RSAPrivateCrtKey privKey)
310307
return new DERSequence(vec).toASN1Primitive().getEncoded(ASN1Encoding.DER);
311308
}
312309

310+
public static byte[] toDerRSAPublicKey(final RSAPublicKey pubKey) throws IOException {
311+
// pubKey.getEncoded() :
312+
return KeyUtil.getEncodedSubjectPublicKeyInfo(
313+
new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE), toASN1Primitive(pubKey)
314+
);
315+
}
316+
313317
public static ASN1Sequence toASN1Primitive(final RSAPublicKey publicKey) {
314318
assert publicKey != null : "null public key";
315319
ASN1EncodableVector vec = new ASN1EncodableVector();
@@ -320,20 +324,7 @@ public static ASN1Sequence toASN1Primitive(final RSAPublicKey publicKey) {
320324

321325
public static byte[] toDerDSAKey(DSAPublicKey pubKey, DSAPrivateKey privKey) throws IOException {
322326
if ( pubKey != null && privKey == null ) {
323-
// pubKey.getEncoded() :
324-
final DSAParams params = pubKey.getParams();
325-
if (params == null) {
326-
return new SubjectPublicKeyInfo(
327-
new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa),
328-
toASN1Primitive(pubKey)
329-
).getEncoded(ASN1Encoding.DER);
330-
}
331-
return new SubjectPublicKeyInfo(
332-
new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa,
333-
new DSAParameter(params.getP(), params.getQ(), params.getG())
334-
),
335-
toASN1Primitive(pubKey)
336-
).getEncoded(ASN1Encoding.DER);
327+
return toDerDSAPublicKey(pubKey);
337328
}
338329
if ( privKey != null && pubKey != null ) {
339330
ASN1EncodableVector vec = new ASN1EncodableVector();
@@ -357,6 +348,23 @@ public static byte[] toDerDSAKey(DSAPublicKey pubKey, DSAPrivateKey privKey) thr
357348
).getEncoded(ASN1Encoding.DER);
358349
}
359350

351+
public static byte[] toDerDSAPublicKey(final DSAPublicKey pubKey) throws IOException {
352+
// pubKey.getEncoded() :
353+
final DSAParams params = pubKey.getParams();
354+
if (params == null) {
355+
return new SubjectPublicKeyInfo(
356+
new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa),
357+
toASN1Primitive(pubKey)
358+
).getEncoded(ASN1Encoding.DER);
359+
}
360+
return new SubjectPublicKeyInfo(
361+
new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa,
362+
new DSAParameter(params.getP(), params.getQ(), params.getG())
363+
),
364+
toASN1Primitive(pubKey)
365+
).getEncoded(ASN1Encoding.DER);
366+
}
367+
360368
public static ASN1Primitive toASN1Primitive(DSAPublicKey pubKey) {
361369
return new ASN1Integer(pubKey.getY());
362370
}

src/test/ruby/rsa/test_rsa.rb

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ def test_RSAPrivateKey_encrypted
231231
}
232232
end
233233

234-
def test_RSAPublicKey
234+
def test_RSAPublicKey_custom
235235
rsa1024 = Fixtures.pkey("rsa1024")
236236

237237
asn1 = OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Integer(rsa1024.n), OpenSSL::ASN1::Integer(rsa1024.e) ])
@@ -265,6 +265,80 @@ def test_RSAPublicKey
265265
assert_equal expected, OpenSSL::Digest::SHA1.hexdigest(key.to_der)
266266
end if !defined?(JRUBY_VERSION) || JRUBY_VERSION > '9.1' # set_key only since Ruby 2.3
267267

268+
def test_RSAPublicKey
269+
rsa1024 = Fixtures.pkey("rsa1024")
270+
rsa1024pub = OpenSSL::PKey::RSA.new(rsa1024.public_to_der)
271+
272+
asn1 = OpenSSL::ASN1::Sequence([
273+
OpenSSL::ASN1::Integer(rsa1024.n),
274+
OpenSSL::ASN1::Integer(rsa1024.e)
275+
])
276+
key = OpenSSL::PKey::RSA.new(asn1.to_der)
277+
assert_not_predicate key, :private?
278+
assert_same_rsa rsa1024pub, key
279+
280+
pem = <<~EOF
281+
-----BEGIN RSA PUBLIC KEY-----
282+
MIGJAoGBAMvCxLDUQKc+1P4+Q6AeFwYDvWfALb+cvzlUEadGoPE6qNWHsLFoo8RF
283+
geyTgE8KQTduu1OE9Zz2SMcRBDu5/1jWtsLPSVrI2ofLLBARUsWanVyki39DeB4u
284+
/xkP2mKGjAokPIwOI3oCthSZlzO9bj3voxTf6XngTqUX8l8URTmHAgMBAAE=
285+
-----END RSA PUBLIC KEY-----
286+
EOF
287+
key = OpenSSL::PKey::RSA.new(pem)
288+
assert_same_rsa rsa1024pub, key
289+
end
290+
291+
def test_export
292+
rsa1024 = Fixtures.pkey("rsa1024")
293+
294+
#pub = OpenSSL::PKey.read(rsa1024.public_to_der) # TODO not supported
295+
pub = OpenSSL::PKey::RSA.new(rsa1024.public_to_der)
296+
assert_not_equal rsa1024.export, pub.export
297+
assert_equal rsa1024.public_to_pem, pub.export
298+
299+
# PKey is immutable in OpenSSL >= 3.0
300+
#if !openssl?(3, 0, 0)
301+
key = OpenSSL::PKey::RSA.new
302+
303+
# key has only n, e and d
304+
key.set_key(rsa1024.n, rsa1024.e, rsa1024.d)
305+
assert_equal rsa1024.public_key.export, key.export
306+
307+
# key has only n, e, d, p and q
308+
key.set_factors(rsa1024.p, rsa1024.q)
309+
assert_equal rsa1024.public_key.export, key.export
310+
311+
# key has n, e, d, p, q, dmp1, dmq1 and iqmp
312+
key.set_crt_params(rsa1024.dmp1, rsa1024.dmq1, rsa1024.iqmp)
313+
#assert_equal rsa1024.export, key.export # TODO does not pass
314+
#end
315+
end
316+
317+
def test_to_der
318+
rsa1024 = Fixtures.pkey("rsa1024")
319+
320+
pub = OpenSSL::PKey::RSA.new(rsa1024.public_to_der)
321+
assert_not_equal rsa1024.to_der, pub.to_der
322+
assert_equal rsa1024.public_to_der, pub.to_der
323+
324+
# PKey is immutable in OpenSSL >= 3.0
325+
#if !openssl?(3, 0, 0)
326+
key = OpenSSL::PKey::RSA.new
327+
328+
# key has only n, e and d
329+
key.set_key(rsa1024.n, rsa1024.e, rsa1024.d)
330+
assert_equal rsa1024.public_key.to_der, key.to_der
331+
332+
# key has only n, e, d, p and q
333+
key.set_factors(rsa1024.p, rsa1024.q)
334+
assert_equal rsa1024.public_key.to_der, key.to_der
335+
336+
# key has n, e, d, p, q, dmp1, dmq1 and iqmp
337+
key.set_crt_params(rsa1024.dmp1, rsa1024.dmq1, rsa1024.iqmp)
338+
#assert_equal rsa1024.to_der, key.to_der # TODO does not pass
339+
#end
340+
end
341+
268342
def test_to_java
269343
key_file = File.join(File.dirname(__FILE__), 'private_key.pem')
270344
pkey = OpenSSL::PKey::RSA.new(File.read(key_file))

0 commit comments

Comments
 (0)