From 57392c33065fe56e106e87520b049d16aeaa90fd Mon Sep 17 00:00:00 2001 From: Erik Berlin Date: Fri, 10 Apr 2026 02:01:04 -0600 Subject: [PATCH] Fix X509::Certificate#dup dropping all extensions --- .../java/org/jruby/ext/openssl/X509Cert.java | 45 ++++++ src/test/ruby/x509/test_x509cert.rb | 148 ++++++++++++++++++ 2 files changed, 193 insertions(+) diff --git a/src/main/java/org/jruby/ext/openssl/X509Cert.java b/src/main/java/org/jruby/ext/openssl/X509Cert.java index 87918f7a..a8103faa 100644 --- a/src/main/java/org/jruby/ext/openssl/X509Cert.java +++ b/src/main/java/org/jruby/ext/openssl/X509Cert.java @@ -292,9 +292,53 @@ public IRubyObject initialize_copy(IRubyObject obj) { if ( this == obj ) return this; checkFrozen(); + super.initialize_copy(obj); + + copyState(getRuntime().getCurrentContext(), (X509Cert) obj); return this; } + private void copyState(final ThreadContext context, final X509Cert that) { + final Ruby runtime = context.runtime; + + this.subject = copyName(context, that.subject); + this.issuer = copyName(context, that.issuer); + this.serial = that.serial; + this.not_before = copyTime(runtime, that.not_before); + this.not_after = copyTime(runtime, that.not_after); + this.sig_alg = that.sig_alg == null ? null : that.sig_alg.dup(); + this.version = that.version; + this.cert = copyCertificate(context, that.cert); + this.public_key = that.public_key == null ? null : (PKey) that.public_key.dup(); + + this.extensions.clear(); + for ( X509Extension ext : that.extensions ) { + this.extensions.add( (X509Extension) ext.dup() ); + } + + this.changed = that.changed; + } + + private static IRubyObject copyName(final ThreadContext context, final IRubyObject name) { + if ( name == null || name.isNil() ) return name; + return X509Name.newName(context.runtime, ((X509Name) name).getX500Name()); + } + + private static RubyTime copyTime(final Ruby runtime, final RubyTime time) { + return time == null ? null : RubyTime.newTime(runtime, time.getJavaDate().getTime()); + } + + private static X509Certificate copyCertificate(final ThreadContext context, final X509Certificate cert) { + if ( cert == null ) return null; + try { + final ByteArrayInputStream bis = new ByteArrayInputStream(cert.getEncoded()); + return (X509Certificate) SecurityHelper.getCertificateFactory("X.509").generateCertificate(bis); + } + catch (CertificateException e) { + throw newCertificateError(context.runtime, e); + } + } + @JRubyMethod public IRubyObject to_der() { try { @@ -724,6 +768,7 @@ public RubyArray extensions() { @SuppressWarnings("unchecked") @JRubyMethod(name = "extensions=") public IRubyObject set_extensions(final IRubyObject array) { + changed = true; extensions.clear(); // RubyArray is a List : extensions.addAll( (List) array ); return array; diff --git a/src/test/ruby/x509/test_x509cert.rb b/src/test/ruby/x509/test_x509cert.rb index 01ac8bbf..8ef7ecbe 100644 --- a/src/test/ruby/x509/test_x509cert.rb +++ b/src/test/ruby/x509/test_x509cert.rb @@ -796,4 +796,152 @@ def test_authority_info_access_ocsp_uris # GH-210 assert_equal ['http://cacerts.digicert.com/DigiCertSHA2SecureServerCA.crt'], cert.ca_issuer_uris assert_not_match(/#