From f62e3e8f59b2f0d7d8e82c41a95e1e09dfd66fb1 Mon Sep 17 00:00:00 2001
From: Kazuki Yamaguchi <k@rhe.jp>
Date: Tue, 28 Jun 2016 23:38:05 +0900
Subject: [PATCH] Implement missing initialize_copy

Implement initialize_copy for:

- OpenSSL::PKCS12
- OpenSSL::SSL::SSLSession
- OpenSSL::X509::Attribute
- OpenSSL::X509::Extension
- OpenSSL::X509::Name
- OpenSSL::X509::Revoked

Remove initialize_copy from:

- OpenSSL::SSL::SSLContext
- OpenSSL::SSL::SSLSocket
- OpenSSL::Engine
- OpenSSL::X509::Store
- OpenSSL::X509::StoreContext

[Bug #12381]
---
 ext/openssl/ossl_engine.c        |  1 +
 ext/openssl/ossl_pkcs12.c        | 20 ++++++++++++++++++++
 ext/openssl/ossl_ssl.c           |  2 ++
 ext/openssl/ossl_ssl_session.c   | 21 +++++++++++++++++++++
 ext/openssl/ossl_x509attr.c      | 20 ++++++++++++++++++++
 ext/openssl/ossl_x509ext.c       | 20 ++++++++++++++++++++
 ext/openssl/ossl_x509name.c      | 20 ++++++++++++++++++++
 ext/openssl/ossl_x509revoked.c   | 20 ++++++++++++++++++++
 ext/openssl/ossl_x509store.c     |  2 ++
 test/openssl/test_engine.rb      |  5 +++++
 test/openssl/test_pkcs12.rb      |  5 +++++
 test/openssl/test_ssl.rb         | 15 ++++++++++++++-
 test/openssl/test_ssl_session.rb |  6 ++++++
 test/openssl/test_x509attr.rb    | 16 ++++++++++++++++
 test/openssl/test_x509ext.rb     |  6 ++++++
 test/openssl/test_x509name.rb    |  5 +++++
 test/openssl/test_x509req.rb     |  5 +++++
 test/openssl/test_x509store.rb   |  7 +++++++
 18 files changed, 195 insertions(+), 1 deletion(-)
 create mode 100644 test/openssl/test_x509attr.rb

diff --git a/ext/openssl/ossl_engine.c b/ext/openssl/ossl_engine.c
index 890ec72..7f40b82 100644
--- a/ext/openssl/ossl_engine.c
+++ b/ext/openssl/ossl_engine.c
@@ -562,6 +562,7 @@ Init_ossl_engine(void)
     rb_define_singleton_method(cEngine, "engines", ossl_engine_s_engines, 0);
     rb_define_singleton_method(cEngine, "by_id", ossl_engine_s_by_id, 1);
     rb_undef_method(CLASS_OF(cEngine), "new");
+    rb_undef_method(cEngine, "initialize_copy");
 
     rb_define_method(cEngine, "id", ossl_engine_get_id, 0);
     rb_define_method(cEngine, "name", ossl_engine_get_name, 0);
diff --git a/ext/openssl/ossl_pkcs12.c b/ext/openssl/ossl_pkcs12.c
index e5052d4..581ce68 100644
--- a/ext/openssl/ossl_pkcs12.c
+++ b/ext/openssl/ossl_pkcs12.c
@@ -65,6 +65,25 @@ ossl_pkcs12_s_allocate(VALUE klass)
     return obj;
 }
 
+static VALUE
+ossl_pkcs12_initialize_copy(VALUE self, VALUE other)
+{
+    PKCS12 *p12, *p12_old, *p12_new;
+
+    rb_check_frozen(self);
+    GetPKCS12(self, p12_old);
+    SafeGetPKCS12(other, p12);
+
+    p12_new = ASN1_dup((i2d_of_void *)i2d_PKCS12, (d2i_of_void *)d2i_PKCS12, (char *)p12);
+    if (!p12_new)
+	ossl_raise(ePKCS12Error, "ASN1_dup");
+
+    SetPKCS12(self, p12_new);
+    PKCS12_free(p12_old);
+
+    return self;
+}
+
 /*
  * call-seq:
  *    PKCS12.create(pass, name, key, cert [, ca, [, key_pbe [, cert_pbe [, key_iter [, mac_iter [, keytype]]]]]])
@@ -222,6 +241,7 @@ Init_ossl_pkcs12(void)
     rb_define_singleton_method(cPKCS12, "create", ossl_pkcs12_s_create, -1);
 
     rb_define_alloc_func(cPKCS12, ossl_pkcs12_s_allocate);
+    rb_define_copy_func(cPKCS12, ossl_pkcs12_initialize_copy);
     rb_attr(cPKCS12, rb_intern("key"), 1, 0, Qfalse);
     rb_attr(cPKCS12, rb_intern("certificate"), 1, 0, Qfalse);
     rb_attr(cPKCS12, rb_intern("ca_certs"), 1, 0, Qfalse);
diff --git a/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c
index 0e01d7c..5c2b123 100644
--- a/ext/openssl/ossl_ssl.c
+++ b/ext/openssl/ossl_ssl.c
@@ -1976,6 +1976,7 @@ Init_ossl_ssl(void)
      */
     cSSLContext = rb_define_class_under(mSSL, "SSLContext", rb_cObject);
     rb_define_alloc_func(cSSLContext, ossl_sslctx_s_alloc);
+    rb_undef_method(cSSLContext, "initialize_copy");
 
     /*
      * Context certificate
@@ -2284,6 +2285,7 @@ Init_ossl_ssl(void)
 #else
     rb_define_const(mSSLExtConfig, "OPENSSL_NO_SOCK", Qfalse);
     rb_define_alloc_func(cSSLSocket, ossl_ssl_s_alloc);
+    rb_undef_method(cSSLSocket, "initialize_copy");
     rb_define_method(cSSLSocket, "connect",    ossl_ssl_connect, 0);
     rb_define_method(cSSLSocket, "connect_nonblock",    ossl_ssl_connect_nonblock, -1);
     rb_define_method(cSSLSocket, "accept",     ossl_ssl_accept, 0);
diff --git a/ext/openssl/ossl_ssl_session.c b/ext/openssl/ossl_ssl_session.c
index e1bbc6f..1e812d5 100644
--- a/ext/openssl/ossl_ssl_session.c
+++ b/ext/openssl/ossl_ssl_session.c
@@ -73,6 +73,26 @@ static VALUE ossl_ssl_session_initialize(VALUE self, VALUE arg1)
 	return self;
 }
 
+static VALUE
+ossl_ssl_session_initialize_copy(VALUE self, VALUE other)
+{
+    SSL_SESSION *sess, *sess_other, *sess_new;
+
+    rb_check_frozen(self);
+    sess = RTYPEDDATA_DATA(self); /* XXX */
+    SafeGetSSLSession(other, sess_other);
+
+    sess_new = ASN1_dup((i2d_of_void *)i2d_SSL_SESSION, (d2i_of_void *)d2i_SSL_SESSION,
+			(char *)sess_other);
+    if (!sess_new)
+	ossl_raise(eSSLSession, "ASN1_dup");
+
+    RTYPEDDATA_DATA(self) = sess_new;
+    SSL_SESSION_free(sess);
+
+    return self;
+}
+
 #if HAVE_SSL_SESSION_CMP == 0
 int SSL_SESSION_cmp(const SSL_SESSION *a,const SSL_SESSION *b)
 {
@@ -309,6 +329,7 @@ void Init_ossl_ssl_session(void)
 
 	rb_define_alloc_func(cSSLSession, ossl_ssl_session_alloc);
 	rb_define_method(cSSLSession, "initialize", ossl_ssl_session_initialize, 1);
+	rb_define_copy_func(cSSLSession, ossl_ssl_session_initialize_copy);
 
 	rb_define_method(cSSLSession, "==", ossl_ssl_session_eq, 1);
 
diff --git a/ext/openssl/ossl_x509attr.c b/ext/openssl/ossl_x509attr.c
index d0f41c6..8a83bda 100644
--- a/ext/openssl/ossl_x509attr.c
+++ b/ext/openssl/ossl_x509attr.c
@@ -130,6 +130,25 @@ ossl_x509attr_initialize(int argc, VALUE *argv, VALUE self)
     return self;
 }
 
+static VALUE
+ossl_x509attr_initialize_copy(VALUE self, VALUE other)
+{
+    X509_ATTRIBUTE *attr, *attr_other, *attr_new;
+
+    rb_check_frozen(self);
+    GetX509Attr(self, attr);
+    SafeGetX509Attr(other, attr_other);
+
+    attr_new = X509_ATTRIBUTE_dup(attr_other);
+    if (!attr_new)
+	ossl_raise(eX509AttrError, "X509_ATTRIBUTE_dup");
+
+    SetX509Attr(self, attr_new);
+    X509_ATTRIBUTE_free(attr);
+
+    return self;
+}
+
 /*
  * call-seq:
  *    attr.oid = string => string
@@ -284,6 +303,7 @@ Init_ossl_x509attr(void)
     cX509Attr = rb_define_class_under(mX509, "Attribute", rb_cObject);
     rb_define_alloc_func(cX509Attr, ossl_x509attr_alloc);
     rb_define_method(cX509Attr, "initialize", ossl_x509attr_initialize, -1);
+    rb_define_copy_func(cX509Attr, ossl_x509attr_initialize_copy);
     rb_define_method(cX509Attr, "oid=", ossl_x509attr_set_oid, 1);
     rb_define_method(cX509Attr, "oid", ossl_x509attr_get_oid, 0);
     rb_define_method(cX509Attr, "value=", ossl_x509attr_set_value, 1);
diff --git a/ext/openssl/ossl_x509ext.c b/ext/openssl/ossl_x509ext.c
index 70a117c..9d22a80 100644
--- a/ext/openssl/ossl_x509ext.c
+++ b/ext/openssl/ossl_x509ext.c
@@ -334,6 +334,25 @@ ossl_x509ext_initialize(int argc, VALUE *argv, VALUE self)
     return self;
 }
 
+static VALUE
+ossl_x509ext_initialize_copy(VALUE self, VALUE other)
+{
+    X509_EXTENSION *ext, *ext_other, *ext_new;
+
+    rb_check_frozen(self);
+    GetX509Ext(self, ext);
+    SafeGetX509Ext(other, ext_other);
+
+    ext_new = X509_EXTENSION_dup(ext_other);
+    if (!ext_new)
+	ossl_raise(eX509ExtError, "X509_EXTENSION_dup");
+
+    SetX509Ext(self, ext_new);
+    X509_EXTENSION_free(ext);
+
+    return self;
+}
+
 static VALUE
 ossl_x509ext_set_oid(VALUE self, VALUE oid)
 {
@@ -488,6 +507,7 @@ Init_ossl_x509ext(void)
     cX509Ext = rb_define_class_under(mX509, "Extension", rb_cObject);
     rb_define_alloc_func(cX509Ext, ossl_x509ext_alloc);
     rb_define_method(cX509Ext, "initialize", ossl_x509ext_initialize, -1);
+    rb_define_copy_func(cX509Ext, ossl_x509ext_initialize_copy);
     rb_define_method(cX509Ext, "oid=", ossl_x509ext_set_oid, 1);
     rb_define_method(cX509Ext, "value=", ossl_x509ext_set_value, 1);
     rb_define_method(cX509Ext, "critical=", ossl_x509ext_set_critical, 1);
diff --git a/ext/openssl/ossl_x509name.c b/ext/openssl/ossl_x509name.c
index a0e28e2..bd69a2a 100644
--- a/ext/openssl/ossl_x509name.c
+++ b/ext/openssl/ossl_x509name.c
@@ -181,6 +181,25 @@ ossl_x509name_initialize(int argc, VALUE *argv, VALUE self)
     return self;
 }
 
+static VALUE
+ossl_x509name_initialize_copy(VALUE self, VALUE other)
+{
+    X509_NAME *name, *name_other, *name_new;
+
+    rb_check_frozen(self);
+    GetX509Name(self, name);
+    SafeGetX509Name(other, name_other);
+
+    name_new = X509_NAME_dup(name_other);
+    if (!name_new)
+	ossl_raise(eX509NameError, "X509_NAME_dup");
+
+    SetX509Name(self, name_new);
+    X509_NAME_free(name);
+
+    return self;
+}
+
 /*
  * call-seq:
  *    name.add_entry(oid, value [, type]) => self
@@ -455,6 +474,7 @@ Init_ossl_x509name(void)
 
     rb_define_alloc_func(cX509Name, ossl_x509name_alloc);
     rb_define_method(cX509Name, "initialize", ossl_x509name_initialize, -1);
+    rb_define_copy_func(cX509Name, ossl_x509name_initialize_copy);
     rb_define_method(cX509Name, "add_entry", ossl_x509name_add_entry, -1);
     rb_define_method(cX509Name, "to_s", ossl_x509name_to_s, -1);
     rb_define_method(cX509Name, "to_a", ossl_x509name_to_a, 0);
diff --git a/ext/openssl/ossl_x509revoked.c b/ext/openssl/ossl_x509revoked.c
index 46250e1..cc3ef60 100644
--- a/ext/openssl/ossl_x509revoked.c
+++ b/ext/openssl/ossl_x509revoked.c
@@ -109,6 +109,25 @@ ossl_x509revoked_initialize(int argc, VALUE *argv, VALUE self)
     return self;
 }
 
+static VALUE
+ossl_x509revoked_initialize_copy(VALUE self, VALUE other)
+{
+    X509_REVOKED *rev, *rev_other, *rev_new;
+
+    rb_check_frozen(self);
+    GetX509Rev(self, rev);
+    SafeGetX509Rev(other, rev_other);
+
+    rev_new = X509_REVOKED_dup(rev_other);
+    if (!rev_new)
+	ossl_raise(eX509RevError, "X509_REVOKED_dup");
+
+    SetX509Rev(self, rev_new);
+    X509_REVOKED_free(rev);
+
+    return self;
+}
+
 static VALUE
 ossl_x509revoked_get_serial(VALUE self)
 {
@@ -234,6 +253,7 @@ Init_ossl_x509revoked(void)
 
     rb_define_alloc_func(cX509Rev, ossl_x509revoked_alloc);
     rb_define_method(cX509Rev, "initialize", ossl_x509revoked_initialize, -1);
+    rb_define_copy_func(cX509Rev, ossl_x509revoked_initialize_copy);
 
     rb_define_method(cX509Rev, "serial", ossl_x509revoked_get_serial, 0);
     rb_define_method(cX509Rev, "serial=", ossl_x509revoked_set_serial, 1);
diff --git a/ext/openssl/ossl_x509store.c b/ext/openssl/ossl_x509store.c
index bb6fe14..b001cbc 100644
--- a/ext/openssl/ossl_x509store.c
+++ b/ext/openssl/ossl_x509store.c
@@ -677,6 +677,7 @@ Init_ossl_x509store(void)
     rb_attr(cX509Store, rb_intern("chain"), 1, 0, Qfalse);
     rb_define_alloc_func(cX509Store, ossl_x509store_alloc);
     rb_define_method(cX509Store, "initialize",   ossl_x509store_initialize, -1);
+    rb_undef_method(cX509Store, "initialize_copy");
     rb_define_method(cX509Store, "verify_callback=", ossl_x509store_set_vfy_cb, 1);
     rb_define_method(cX509Store, "flags=",       ossl_x509store_set_flags, 1);
     rb_define_method(cX509Store, "purpose=",     ossl_x509store_set_purpose, 1);
@@ -693,6 +694,7 @@ Init_ossl_x509store(void)
     x509stctx = cX509StoreContext;
     rb_define_alloc_func(cX509StoreContext, ossl_x509stctx_alloc);
     rb_define_method(x509stctx,"initialize",  ossl_x509stctx_initialize, -1);
+    rb_undef_method(x509stctx, "initialize_copy");
     rb_define_method(x509stctx,"verify",      ossl_x509stctx_verify, 0);
     rb_define_method(x509stctx,"chain",       ossl_x509stctx_get_chain,0);
     rb_define_method(x509stctx,"error",       ossl_x509stctx_get_err, 0);
diff --git a/test/openssl/test_engine.rb b/test/openssl/test_engine.rb
index 3521de6..d7e1e2f 100644
--- a/test/openssl/test_engine.rb
+++ b/test/openssl/test_engine.rb
@@ -59,6 +59,11 @@ def test_openssl_engine_cipher_rc4
     end
   end
 
+  def test_dup
+    engine = get_engine
+    assert_raise(NoMethodError) { engine.dup }
+  end
+
   private
 
   def get_engine
diff --git a/test/openssl/test_pkcs12.rb b/test/openssl/test_pkcs12.rb
index ba07d76..9af4f49 100644
--- a/test/openssl/test_pkcs12.rb
+++ b/test/openssl/test_pkcs12.rb
@@ -180,6 +180,11 @@ def test_create_with_mac_itr
       end
     end
 
+    def test_dup
+      p12 = OpenSSL::PKCS12.create("pass", "name", TEST_KEY_RSA1024, @mycert)
+      assert_equal p12.to_der, p12.dup.to_der
+    end
+
     private
     def assert_cert expected, actual
       [
diff --git a/test/openssl/test_ssl.rb b/test/openssl/test_ssl.rb
index d5e3547..235fe69 100644
--- a/test/openssl/test_ssl.rb
+++ b/test/openssl/test_ssl.rb
@@ -848,7 +848,7 @@ def test_tlsext_hostname
     return unless OpenSSL::SSL::SSLSocket.instance_methods.include?(:hostname)
 
     ctx_proc = Proc.new do |ctx, ssl|
-      foo_ctx = ctx.dup
+      foo_ctx = OpenSSL::SSL::SSLContext.new
 
       ctx.servername_cb = Proc.new do |ssl2, hostname|
         case hostname
@@ -1169,6 +1169,19 @@ def test_sync_close_without_connect
     }
   end
 
+  def test_dup
+    ctx = OpenSSL::SSL::SSLContext.new
+    sock1, sock2 = socketpair
+    ssl = OpenSSL::SSL::SSLSocket.new(sock1, ctx)
+
+    assert_raise(NoMethodError) { ctx.dup }
+    assert_raise(NoMethodError) { ssl.dup }
+  ensure
+    ssl.close if ssl
+    sock1.close
+    sock2.close
+  end
+
   private
 
   def start_server_version(version, ctx_proc=nil, server_proc=nil, &blk)
diff --git a/test/openssl/test_ssl_session.rb b/test/openssl/test_ssl_session.rb
index ee1a907..af047c5 100644
--- a/test/openssl/test_ssl_session.rb
+++ b/test/openssl/test_ssl_session.rb
@@ -376,6 +376,12 @@ def test_ctx_server_session_cb
     assert(called[:get1])
     assert(called[:get2])
   end
+
+  def test_dup
+    sess_orig = OpenSSL::SSL::Session.new(DUMMY_SESSION)
+    sess_dup = sess_orig.dup
+    assert_equal(sess_orig.to_der, sess_dup.to_der)
+  end
 end
 
 end
diff --git a/test/openssl/test_x509attr.rb b/test/openssl/test_x509attr.rb
new file mode 100644
index 0000000..1d688e2
--- /dev/null
+++ b/test/openssl/test_x509attr.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: false
+require_relative "utils"
+
+if defined?(OpenSSL::TestUtils)
+
+class OpenSSL::TestX509Attribute < Test::Unit::TestCase
+  def test_dup
+    val = OpenSSL::ASN1::Set([
+      OpenSSL::ASN1::UTF8String("abc123")
+    ])
+    attr = OpenSSL::X509::Attribute.new("challengePassword", val)
+    assert_equal(attr.to_der, attr.dup.to_der)
+  end
+end
+
+end
diff --git a/test/openssl/test_x509ext.rb b/test/openssl/test_x509ext.rb
index e6d49bb..f7ca2a5 100644
--- a/test/openssl/test_x509ext.rb
+++ b/test/openssl/test_x509ext.rb
@@ -65,6 +65,12 @@ def test_create_by_factory
       %r{URI:ldap://ldap.example.com/cn=ca\?certificateRevocationList;binary},
       cdp.value)
   end
+
+  def test_dup
+    ext = OpenSSL::X509::Extension.new(@basic_constraints.to_der)
+    assert_equal(@basic_constraints.to_der, ext.to_der)
+    assert_equal(ext.to_der, ext.dup.to_der)
+  end
 end
 
 end
diff --git a/test/openssl/test_x509name.rb b/test/openssl/test_x509name.rb
index 56e7987..ddcbd0e 100644
--- a/test/openssl/test_x509name.rb
+++ b/test/openssl/test_x509name.rb
@@ -363,6 +363,11 @@ def test_hash
     expected = (d[0].ord & 0xff) | (d[1].ord & 0xff) << 8 | (d[2].ord & 0xff) << 16 | (d[3].ord & 0xff) << 24
     assert_equal(expected, name_hash(name))
   end
+
+  def test_dup
+    name = OpenSSL::X509::Name.parse("/CN=ruby-lang.org")
+    assert_equal(name.to_der, name.dup.to_der)
+  end
 end
 
 end
diff --git a/test/openssl/test_x509req.rb b/test/openssl/test_x509req.rb
index f1bf184..2e072ae 100644
--- a/test/openssl/test_x509req.rb
+++ b/test/openssl/test_x509req.rb
@@ -147,6 +147,11 @@ def test_sign_and_verify_dsa_md5
       issue_csr(0, @dn, @dsa512, OpenSSL::Digest::MD5.new) }
   end
 
+  def test_dup
+    req = issue_csr(0, @dn, @rsa1024, OpenSSL::Digest::SHA1.new)
+    assert_equal(req.to_der, req.dup.to_der)
+  end
+
   private
 
   def request_error_returns_false
diff --git a/test/openssl/test_x509store.rb b/test/openssl/test_x509store.rb
index 9964cc8..3a81617 100644
--- a/test/openssl/test_x509store.rb
+++ b/test/openssl/test_x509store.rb
@@ -227,6 +227,13 @@ def test_set_errors
       }
     end
   end
+
+  def test_dup
+    store = OpenSSL::X509::Store.new
+    assert_raise(NoMethodError) { store.dup }
+    ctx = OpenSSL::X509::StoreContext.new(store)
+    assert_raise(NoMethodError) { ctx.dup }
+  end
 end
 
 end
-- 
2.9.2.603.gefbf09b.dirty

