From 7dbac3cf9aacd7aa2b4cd3252340b80a8263ad6f Mon Sep 17 00:00:00 2001 From: kares Date: Fri, 13 Dec 2024 15:20:15 +0100 Subject: [PATCH] [compat] implement PKey::RSA public_to_der and public_to_pem --- .../java/org/jruby/ext/openssl/PKeyRSA.java | 38 +++++++++- .../java/org/jruby/ext/openssl/impl/PKey.java | 46 ++++++----- src/test/ruby/rsa/test_rsa.rb | 76 ++++++++++++++++++- 3 files changed, 136 insertions(+), 24 deletions(-) diff --git a/src/main/java/org/jruby/ext/openssl/PKeyRSA.java b/src/main/java/org/jruby/ext/openssl/PKeyRSA.java index 883e9c72..66a0ee47 100644 --- a/src/main/java/org/jruby/ext/openssl/PKeyRSA.java +++ b/src/main/java/org/jruby/ext/openssl/PKeyRSA.java @@ -77,6 +77,7 @@ import static org.jruby.ext.openssl.impl.PKey.readRSAPublicKey; import static org.jruby.ext.openssl.impl.PKey.toASN1Primitive; import static org.jruby.ext.openssl.impl.PKey.toDerRSAKey; +import static org.jruby.ext.openssl.impl.PKey.toDerRSAPublicKey; /** * @author Ola Bini @@ -356,6 +357,21 @@ public RubyBoolean private_p() { return getRuntime().newBoolean(isPrivateKey()); } + @JRubyMethod(name = "public_to_der") + public RubyString public_to_der(ThreadContext context) { + final byte[] bytes; + try { + bytes = toDerRSAPublicKey(publicKey); + } + catch (NoClassDefFoundError e) { + throw newRSAError(context.runtime, bcExceptionMessage(e)); + } + catch (Exception e) { + throw newRSAError(getRuntime(), e.getMessage(), e); + } + return StringHelper.newString(context.runtime, bytes); + } + @Override @JRubyMethod(name = "to_der") public RubyString to_der() { @@ -366,8 +382,8 @@ public RubyString to_der() { catch (NoClassDefFoundError e) { throw newRSAError(getRuntime(), bcExceptionMessage(e)); } - catch (IOException e) { - throw newRSAError(getRuntime(), e.getMessage()); + catch (Exception e) { + throw newRSAError(getRuntime(), e.getMessage(), e); } return StringHelper.newString(getRuntime(), bytes); } @@ -470,6 +486,21 @@ public RubyString to_pem(ThreadContext context, final IRubyObject[] args) { } } + @JRubyMethod + public RubyString public_to_pem(ThreadContext context) { + try { + final StringWriter writer = new StringWriter(); + PEMInputOutput.writeRSAPublicKey(writer, publicKey); + return RubyString.newString(context.runtime, writer.getBuffer()); + } + catch (NoClassDefFoundError ncdfe) { + throw newRSAError(context.runtime, bcExceptionMessage(ncdfe)); + } + catch (IOException ioe) { + throw newRSAError(context.runtime, ioe.getMessage()); + } + } + private String getPadding(final int padding) { if ( padding < 1 || padding > 4 ) { throw newRSAError(getRuntime(), ""); @@ -745,6 +776,7 @@ public IRubyObject set_key(final ThreadContext context, IRubyObject n, IRubyObje this.rsa_n = BN.getBigInteger(n); this.rsa_e = BN.getBigInteger(e); this.rsa_d = BN.getBigInteger(d); + generatePublicKeyIfParams(context); generatePrivateKeyIfParams(context); return this; } @@ -769,8 +801,6 @@ public IRubyObject set_crt_params(final ThreadContext context, IRubyObject dmp1, private void generatePublicKeyIfParams(final ThreadContext context) { final Ruby runtime = context.runtime; - if ( publicKey != null ) throw newRSAError(runtime, "illegal modification"); - // Don't access the rsa_n and rsa_e fields directly. They may have // already been consumed and cleared by generatePrivateKeyIfParams. BigInteger _rsa_n = getModulus(); diff --git a/src/main/java/org/jruby/ext/openssl/impl/PKey.java b/src/main/java/org/jruby/ext/openssl/impl/PKey.java index a6a1ba05..c50726ae 100644 --- a/src/main/java/org/jruby/ext/openssl/impl/PKey.java +++ b/src/main/java/org/jruby/ext/openssl/impl/PKey.java @@ -138,7 +138,7 @@ public static KeyPair readPrivateKey(final Type type, final PrivateKeyInfo keyIn } // d2i_PUBKEY_bio - public static PublicKey readPublicKey(byte[] input) throws IOException { + public static PublicKey readPublicKey(final byte[] input) throws IOException { try (Reader in = new InputStreamReader(new ByteArrayInputStream(input))) { Object pemObject = new PEMParser(in).readObject(); SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfo.getInstance(pemObject); @@ -292,10 +292,7 @@ public static KeyPair readECPrivateKey(final KeyFactory keyFactory, final Privat public static byte[] toDerRSAKey(RSAPublicKey pubKey, RSAPrivateCrtKey privKey) throws IOException { if ( pubKey != null && privKey == null ) { - // pubKey.getEncoded() : - return KeyUtil.getEncodedSubjectPublicKeyInfo( - new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE), toASN1Primitive(pubKey) - ); + return toDerRSAPublicKey(pubKey); } ASN1EncodableVector vec = new ASN1EncodableVector(); vec.add(new ASN1Integer(BigInteger.ZERO)); @@ -310,6 +307,13 @@ public static byte[] toDerRSAKey(RSAPublicKey pubKey, RSAPrivateCrtKey privKey) return new DERSequence(vec).toASN1Primitive().getEncoded(ASN1Encoding.DER); } + public static byte[] toDerRSAPublicKey(final RSAPublicKey pubKey) throws IOException { + // pubKey.getEncoded() : + return KeyUtil.getEncodedSubjectPublicKeyInfo( + new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE), toASN1Primitive(pubKey) + ); + } + public static ASN1Sequence toASN1Primitive(final RSAPublicKey publicKey) { assert publicKey != null : "null public key"; ASN1EncodableVector vec = new ASN1EncodableVector(); @@ -320,20 +324,7 @@ public static ASN1Sequence toASN1Primitive(final RSAPublicKey publicKey) { public static byte[] toDerDSAKey(DSAPublicKey pubKey, DSAPrivateKey privKey) throws IOException { if ( pubKey != null && privKey == null ) { - // pubKey.getEncoded() : - final DSAParams params = pubKey.getParams(); - if (params == null) { - return new SubjectPublicKeyInfo( - new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa), - toASN1Primitive(pubKey) - ).getEncoded(ASN1Encoding.DER); - } - return new SubjectPublicKeyInfo( - new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa, - new DSAParameter(params.getP(), params.getQ(), params.getG()) - ), - toASN1Primitive(pubKey) - ).getEncoded(ASN1Encoding.DER); + return toDerDSAPublicKey(pubKey); } if ( privKey != null && pubKey != null ) { ASN1EncodableVector vec = new ASN1EncodableVector(); @@ -357,6 +348,23 @@ public static byte[] toDerDSAKey(DSAPublicKey pubKey, DSAPrivateKey privKey) thr ).getEncoded(ASN1Encoding.DER); } + public static byte[] toDerDSAPublicKey(final DSAPublicKey pubKey) throws IOException { + // pubKey.getEncoded() : + final DSAParams params = pubKey.getParams(); + if (params == null) { + return new SubjectPublicKeyInfo( + new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa), + toASN1Primitive(pubKey) + ).getEncoded(ASN1Encoding.DER); + } + return new SubjectPublicKeyInfo( + new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa, + new DSAParameter(params.getP(), params.getQ(), params.getG()) + ), + toASN1Primitive(pubKey) + ).getEncoded(ASN1Encoding.DER); + } + public static ASN1Primitive toASN1Primitive(DSAPublicKey pubKey) { return new ASN1Integer(pubKey.getY()); } diff --git a/src/test/ruby/rsa/test_rsa.rb b/src/test/ruby/rsa/test_rsa.rb index 85186336..62ebbacb 100644 --- a/src/test/ruby/rsa/test_rsa.rb +++ b/src/test/ruby/rsa/test_rsa.rb @@ -231,7 +231,7 @@ def test_RSAPrivateKey_encrypted } end - def test_RSAPublicKey + def test_RSAPublicKey_custom rsa1024 = Fixtures.pkey("rsa1024") asn1 = OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Integer(rsa1024.n), OpenSSL::ASN1::Integer(rsa1024.e) ]) @@ -265,6 +265,80 @@ def test_RSAPublicKey assert_equal expected, OpenSSL::Digest::SHA1.hexdigest(key.to_der) end if !defined?(JRUBY_VERSION) || JRUBY_VERSION > '9.1' # set_key only since Ruby 2.3 + def test_RSAPublicKey + rsa1024 = Fixtures.pkey("rsa1024") + rsa1024pub = OpenSSL::PKey::RSA.new(rsa1024.public_to_der) + + asn1 = OpenSSL::ASN1::Sequence([ + OpenSSL::ASN1::Integer(rsa1024.n), + OpenSSL::ASN1::Integer(rsa1024.e) + ]) + key = OpenSSL::PKey::RSA.new(asn1.to_der) + assert_not_predicate key, :private? + assert_same_rsa rsa1024pub, key + + pem = <<~EOF + -----BEGIN RSA PUBLIC KEY----- + MIGJAoGBAMvCxLDUQKc+1P4+Q6AeFwYDvWfALb+cvzlUEadGoPE6qNWHsLFoo8RF + geyTgE8KQTduu1OE9Zz2SMcRBDu5/1jWtsLPSVrI2ofLLBARUsWanVyki39DeB4u + /xkP2mKGjAokPIwOI3oCthSZlzO9bj3voxTf6XngTqUX8l8URTmHAgMBAAE= + -----END RSA PUBLIC KEY----- + EOF + key = OpenSSL::PKey::RSA.new(pem) + assert_same_rsa rsa1024pub, key + end + + def test_export + rsa1024 = Fixtures.pkey("rsa1024") + + #pub = OpenSSL::PKey.read(rsa1024.public_to_der) # TODO not supported + pub = OpenSSL::PKey::RSA.new(rsa1024.public_to_der) + assert_not_equal rsa1024.export, pub.export + assert_equal rsa1024.public_to_pem, pub.export + + # PKey is immutable in OpenSSL >= 3.0 + #if !openssl?(3, 0, 0) + key = OpenSSL::PKey::RSA.new + + # key has only n, e and d + key.set_key(rsa1024.n, rsa1024.e, rsa1024.d) + assert_equal rsa1024.public_key.export, key.export + + # key has only n, e, d, p and q + key.set_factors(rsa1024.p, rsa1024.q) + assert_equal rsa1024.public_key.export, key.export + + # key has n, e, d, p, q, dmp1, dmq1 and iqmp + key.set_crt_params(rsa1024.dmp1, rsa1024.dmq1, rsa1024.iqmp) + #assert_equal rsa1024.export, key.export # TODO does not pass + #end + end + + def test_to_der + rsa1024 = Fixtures.pkey("rsa1024") + + pub = OpenSSL::PKey::RSA.new(rsa1024.public_to_der) + assert_not_equal rsa1024.to_der, pub.to_der + assert_equal rsa1024.public_to_der, pub.to_der + + # PKey is immutable in OpenSSL >= 3.0 + #if !openssl?(3, 0, 0) + key = OpenSSL::PKey::RSA.new + + # key has only n, e and d + key.set_key(rsa1024.n, rsa1024.e, rsa1024.d) + assert_equal rsa1024.public_key.to_der, key.to_der + + # key has only n, e, d, p and q + key.set_factors(rsa1024.p, rsa1024.q) + assert_equal rsa1024.public_key.to_der, key.to_der + + # key has n, e, d, p, q, dmp1, dmq1 and iqmp + key.set_crt_params(rsa1024.dmp1, rsa1024.dmq1, rsa1024.iqmp) + #assert_equal rsa1024.to_der, key.to_der # TODO does not pass + #end + end + def test_to_java key_file = File.join(File.dirname(__FILE__), 'private_key.pem') pkey = OpenSSL::PKey::RSA.new(File.read(key_file))