[DRE-commits] [ruby-openssl] 01/01: New upstream version 2.0.4

Antonio Terceiro terceiro at moszumanska.debian.org
Sat Jul 1 22:45:31 UTC 2017


This is an automated email from the git hooks/post-receive script.

terceiro pushed a commit to branch master
in repository ruby-openssl.

commit 04f2764a4af2282047e33e2e8a633e16a6f87341
Author: Antonio Terceiro <terceiro at debian.org>
Date:   Sat Jul 1 19:44:56 2017 -0300

    New upstream version 2.0.4
---
 .gitignore                          |   10 +
 .travis.yml                         |   37 +
 BSDL                                |   22 +
 CONTRIBUTING.md                     |  130 ++
 Dockerfile                          |    1 +
 History.md                          |  185 +++
 LICENSE.txt                         |   56 +
 README.md                           |   66 +
 Rakefile                            |   73 +
 appveyor.yml                        |   16 +
 docker-compose.yml                  |   13 +
 ext/openssl/deprecation.rb          |   26 +
 ext/openssl/extconf.rb              |  201 +++
 ext/openssl/openssl_missing.c       |  173 +++
 ext/openssl/openssl_missing.h       |  265 ++++
 ext/openssl/ossl.c                  | 1140 +++++++++++++++
 ext/openssl/ossl.h                  |  186 +++
 ext/openssl/ossl_asn1.c             | 1938 ++++++++++++++++++++++++
 ext/openssl/ossl_asn1.h             |   66 +
 ext/openssl/ossl_bio.c              |   84 ++
 ext/openssl/ossl_bio.h              |   19 +
 ext/openssl/ossl_bn.c               | 1160 +++++++++++++++
 ext/openssl/ossl_bn.h               |   25 +
 ext/openssl/ossl_cipher.c           | 1084 ++++++++++++++
 ext/openssl/ossl_cipher.h           |   20 +
 ext/openssl/ossl_config.c           |   89 ++
 ext/openssl/ossl_config.h           |   19 +
 ext/openssl/ossl_digest.c           |  460 ++++++
 ext/openssl/ossl_digest.h           |   20 +
 ext/openssl/ossl_engine.c           |  563 +++++++
 ext/openssl/ossl_engine.h           |   19 +
 ext/openssl/ossl_hmac.c             |  398 +++++
 ext/openssl/ossl_hmac.h             |   18 +
 ext/openssl/ossl_ns_spki.c          |  401 +++++
 ext/openssl/ossl_ns_spki.h          |   19 +
 ext/openssl/ossl_ocsp.c             | 2013 +++++++++++++++++++++++++
 ext/openssl/ossl_ocsp.h             |   23 +
 ext/openssl/ossl_pkcs12.c           |  261 ++++
 ext/openssl/ossl_pkcs12.h           |   13 +
 ext/openssl/ossl_pkcs5.c            |  180 +++
 ext/openssl/ossl_pkcs5.h            |    6 +
 ext/openssl/ossl_pkcs7.c            | 1125 ++++++++++++++
 ext/openssl/ossl_pkcs7.h            |   20 +
 ext/openssl/ossl_pkey.c             |  483 ++++++
 ext/openssl/ossl_pkey.h             |  244 ++++
 ext/openssl/ossl_pkey_dh.c          |  650 +++++++++
 ext/openssl/ossl_pkey_dsa.c         |  670 +++++++++
 ext/openssl/ossl_pkey_ec.c          | 1824 +++++++++++++++++++++++
 ext/openssl/ossl_pkey_rsa.c         |  766 ++++++++++
 ext/openssl/ossl_rand.c             |  238 +++
 ext/openssl/ossl_rand.h             |   18 +
 ext/openssl/ossl_ssl.c              | 2751 +++++++++++++++++++++++++++++++++++
 ext/openssl/ossl_ssl.h              |   41 +
 ext/openssl/ossl_ssl_session.c      |  334 +++++
 ext/openssl/ossl_version.h          |   15 +
 ext/openssl/ossl_x509.c             |  186 +++
 ext/openssl/ossl_x509.h             |  122 ++
 ext/openssl/ossl_x509attr.c         |  328 +++++
 ext/openssl/ossl_x509cert.c         |  860 +++++++++++
 ext/openssl/ossl_x509crl.c          |  546 +++++++
 ext/openssl/ossl_x509ext.c          |  480 ++++++
 ext/openssl/ossl_x509name.c         |  545 +++++++
 ext/openssl/ossl_x509req.c          |  478 ++++++
 ext/openssl/ossl_x509revoked.c      |  279 ++++
 ext/openssl/ossl_x509store.c        |  913 ++++++++++++
 ext/openssl/ruby_missing.h          |   23 +
 lib/openssl.rb                      |   21 +
 lib/openssl/bn.rb                   |   39 +
 lib/openssl/buffering.rb            |  459 ++++++
 lib/openssl/cipher.rb               |   67 +
 lib/openssl/config.rb               |  473 ++++++
 lib/openssl/digest.rb               |   78 +
 lib/openssl/pkey.rb                 |   44 +
 lib/openssl/ssl.rb                  |  400 +++++
 lib/openssl/x509.rb                 |  176 +++
 openssl.gemspec                     |   24 +
 sample/c_rehash.rb                  |  174 +++
 sample/cert2text.rb                 |   23 +
 sample/certstore.rb                 |  161 ++
 sample/cipher.rb                    |   54 +
 sample/crlstore.rb                  |  122 ++
 sample/echo_cli.rb                  |   44 +
 sample/echo_svr.rb                  |   65 +
 sample/gen_csr.rb                   |   51 +
 sample/smime_read.rb                |   23 +
 sample/smime_write.rb               |   23 +
 sample/wget.rb                      |   34 +
 test/envutil.rb                     |  316 ++++
 test/test_asn1.rb                   |  586 ++++++++
 test/test_bn.rb                     |   61 +
 test/test_buffering.rb              |   89 ++
 test/test_cipher.rb                 |  318 ++++
 test/test_config.rb                 |  300 ++++
 test/test_digest.rb                 |  134 ++
 test/test_engine.rb                 |   99 ++
 test/test_fips.rb                   |   15 +
 test/test_hmac.rb                   |   40 +
 test/test_ns_spki.rb                |   53 +
 test/test_ocsp.rb                   |  298 ++++
 test/test_pair.rb                   |  493 +++++++
 test/test_pkcs12.rb                 |  315 ++++
 test/test_pkcs5.rb                  |   98 ++
 test/test_pkcs7.rb                  |  289 ++++
 test/test_pkey_dh.rb                |  120 ++
 test/test_pkey_dsa.rb               |  200 +++
 test/test_pkey_ec.rb                |  329 +++++
 test/test_pkey_rsa.rb               |  259 ++++
 test/test_random.rb                 |   15 +
 test/test_ssl.rb                    | 1300 +++++++++++++++++
 test/test_ssl_session.rb            |  380 +++++
 test/test_x509attr.rb               |   67 +
 test/test_x509cert.rb               |  190 +++
 test/test_x509crl.rb                |  217 +++
 test/test_x509ext.rb                |   80 +
 test/test_x509name.rb               |  376 +++++
 test/test_x509req.rb                |  165 +++
 test/test_x509store.rb              |  257 ++++
 test/ut_eof.rb                      |  129 ++
 test/utils.rb                       |  380 +++++
 tool/ruby-openssl-docker/Dockerfile |   94 ++
 tool/ruby-openssl-docker/README.md  |    6 +
 tool/ruby-openssl-docker/init.sh    |   20 +
 tool/sync-with-trunk                |  106 ++
 tool/update-gh-pages                |   22 +
 124 files changed, 36208 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1be0883
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,10 @@
+/doc/
+/pkg/
+/tmp/
+/html/
+*.bundle
+*.so
+*.o
+ext/openssl/mkmf.log
+ext/openssl/Makefile
+ext/openssl/extconf.h
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..99cd60d
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,37 @@
+language: c
+sudo: required
+group: edge
+dist: trusty
+services:
+  - docker
+before_install:
+  - sudo apt-get -qq update
+install:
+  - sudo sh -c "curl -L https://github.com/docker/compose/releases/download/1.8.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose"
+  - sudo chmod +x /usr/local/bin/docker-compose
+script:
+  - docker -v
+  - docker-compose -v
+  - docker-compose build --no-cache
+  - docker-compose run test
+matrix:
+  fast_finish: true
+  include:
+    - env: RUBY_VERSION=ruby-2.3 OPENSSL_VERSION=openssl-1.0.2
+    - env: RUBY_VERSION=ruby-2.4 OPENSSL_VERSION=openssl-1.0.0
+    - env: RUBY_VERSION=ruby-2.4 OPENSSL_VERSION=openssl-1.0.1
+    - env: RUBY_VERSION=ruby-2.4 OPENSSL_VERSION=openssl-1.0.2
+    - env: RUBY_VERSION=ruby-2.4 OPENSSL_VERSION=openssl-1.1.0
+    - env: RUBY_VERSION=ruby-2.4 OPENSSL_VERSION=libressl-2.3
+    - env: RUBY_VERSION=ruby-2.4 OPENSSL_VERSION=libressl-2.4
+    - env: RUBY_VERSION=ruby-2.4 OPENSSL_VERSION=libressl-2.5
+    - language: ruby
+      rvm: ruby-head
+      before_install:
+        - "rake install_dependencies"
+      script:
+        - "rake compile -- --enable-debug"
+        - "rake test"
+  allow_failures:
+    - language: ruby
+      rvm: ruby-head
diff --git a/BSDL b/BSDL
new file mode 100644
index 0000000..a009cae
--- /dev/null
+++ b/BSDL
@@ -0,0 +1,22 @@
+Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..6e6e2a5
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,130 @@
+# Contributing to Ruby OpenSSL
+
+Thank you for your interest in contributing to Ruby OpenSSL!
+
+This documentation provides an overview how you can contribute.
+
+## Bugs and feature requests
+
+Bugs and feature requests are tracked on [GitHub].
+
+If you think you found a bug, file a ticket on GitHub. Please DO NOT report
+security issues here, there is a separate procedure which is described on
+["Security at ruby-lang.org"](https://www.ruby-lang.org/en/security/).
+
+When reporting a bug, please make sure you include the version of Ruby, the
+version of openssl gem, the version of the OpenSSL library, along with a sample
+file that illustrates the problem or link to repository or gem that is
+associated with the bug.
+
+There is a number of unresolved issues and feature requests for openssl that
+need review. Before submitting a new ticket, it is recommended to check
+[known issues] and [bugs.ruby-lang.org], the previous issue tracker.
+
+## Submitting patches
+
+Patches are also very welcome!
+
+Please submit a [pull request] with your changes.
+
+Make sure that your branch does:
+
+* Have good commit messages
+* Follow Ruby's coding style ([DeveloperHowTo])
+* Pass the test suite successfully (see "Testing")
+* Add an entry to [History.md] if necessary
+
+## Testing
+
+We have a test suite!
+
+Test cases are located under the
+[`test/`](https://github.com/ruby/openssl/tree/master/test) directory.
+
+You can run it with the following three commands:
+
+```
+$ rake install_dependencies # installs rake-compiler, test-unit, ...
+$ rake compile
+$ rake test
+```
+
+### Docker
+
+You can also use Docker Compose to run tests. It can be used to check that your
+changes work correctly with various supported versions of Ruby and OpenSSL.
+
+First, you need to install [Docker](https://www.docker.com/products/docker) and
+[Docker Compose](https://www.docker.com/products/docker-compose) on your
+computer.
+
+If you're on MacOS or Windows, we recommended to use the official [Docker
+Toolbox](https://www.docker.com/products/docker-toolbox). On Linux, follow the
+instructions for your package manager. For further information, please check
+the [official documentation](https://docs.docker.com/).
+
+Once you have Docker and Docker Compose, running the following commands will
+build the container and execute the openssl tests. In this example, we will use
+Ruby version 2.3 with OpenSSL version 1.0.2.
+
+```
+$ docker-compose build
+$ export RUBY_VERSION=ruby-2.3
+$ export OPENSSL_VERSION=openssl-1.0.2
+$ docker-compose run test
+
+# You may want an interactive shell for dubugging
+$ docker-compose run debug
+```
+
+All possible values for `RUBY_VERSION` and `OPENSSL_VERSION` can be found in
+[`.travis.yml`](https://github.com/ruby/openssl/tree/master/.travis.yml).
+
+**NOTE**: these commands must be run from the openssl repository root, in order
+to use the
+[`docker-compose.yml`](https://github.com/ruby/openssl/blob/master/docker-compose.yml)
+file we have provided.
+
+This Docker image is built using the
+[Dockerfile](https://github.com/ruby/openssl/tree/master/tool/ruby-openssl-docker)
+provided in the repository.
+
+
+## Relation with Ruby source tree
+
+After Ruby 2.3, `ext/openssl` was converted into a "default gem", a library
+which ships with standard Ruby builds but can be upgraded via RubyGems. This
+means the development of this gem has migrated to a [separate
+repository][GitHub] and will be released independently.
+
+The version included in the Ruby source tree (trunk branch) is synchronized with
+the latest release.
+
+## Release policy
+
+Bug fixes (including security fixes) will be made only for the version series
+included in a stable Ruby release.
+
+## Security
+
+If you discovered a security issue, please send us in private, using the
+security issue handling procedure for Ruby core.
+
+You can either use [HackerOne] or send an email to security at ruby-lang.org.
+
+Please see [Security] page on ruby-lang.org website for details.
+
+Reported problems will be published after a fix is released.
+
+_Thanks for your contributions!_
+
+  _\- The Ruby OpenSSL team_
+
+[GitHub]: https://github.com/ruby/openssl
+[known issues]: https://github.com/ruby/openssl/issues
+[bugs.ruby-lang.org]: https://bugs.ruby-lang.org/issues?utf8=%E2%9C%93&set_filter=1&f%5B%5D=status_id&op%5Bstatus_id%5D=o&f%5B%5D=assigned_to_id&op%5Bassigned_to_id%5D=%3D&v%5Bassigned_to_id%5D%5B%5D=7150&f%5B%5D=&c%5B%5D=project&c%5B%5D=tracker&c%5B%5D=status&c%5B%5D=subject&c%5B%5D=assigned_to&c%5B%5D=updated_on&group_by=&t%5B%5D=
+[DeveloperHowTo]: https://bugs.ruby-lang.org/projects/ruby/wiki/DeveloperHowto
+[HackerOne]: https://hackerone.com/ruby
+[Security]: https://www.ruby-lang.org/en/security/
+[pull request]: https://github.com/ruby/openssl/compare
+[History.md]: https://github.com/ruby/openssl/tree/master/History.md
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..ab7f09e
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1 @@
+FROM zzak/ruby-openssl-docker:2.0
diff --git a/History.md b/History.md
new file mode 100644
index 0000000..bb49933
--- /dev/null
+++ b/History.md
@@ -0,0 +1,185 @@
+Version 2.0.4
+=============
+
+Bug fixes
+---------
+
+* It now compiles with LibreSSL without renaming on Windows (mswin).
+* A workaround for the error queue leak of X509_load_cert_crl_file() that
+  causes random errors is added.
+  [[Bug #11033]](https://bugs.ruby-lang.org/issues/11033)
+
+
+Version 2.0.3
+=============
+
+Bug fixes
+---------
+
+* OpenSSL::ASN1::Constructive#each which was broken by 2.0.0 is fixed.
+  [[ruby/openssl#96]](https://github.com/ruby/openssl/pull/96)
+* Fixed build with static OpenSSL libraries on Windows.
+  [[Bug #13080]](https://bugs.ruby-lang.org/issues/13080)
+* OpenSSL::X509::Name#eql? which was broken by 2.0.0 is fixed.
+
+
+Version 2.0.2
+=============
+
+Bug fixes
+---------
+
+* Fix build with early 0.9.8 series which did not have SSL_CTX_clear_options().
+  [ruby-core:78693]
+
+
+Version 2.0.1
+=============
+
+Bug fixes
+---------
+
+* A GC issue around OpenSSL::BN is fixed.
+  [[ruby/openssl#87]](https://github.com/ruby/openssl/issues/87)
+* OpenSSL::ASN1 now parses BER encoding of GeneralizedTime without seconds.
+  [[ruby/openssl#88]](https://github.com/ruby/openssl/pull/88)
+
+
+Version 2.0.0
+=============
+
+This is the first release of openssl gem, formerly a standard library of Ruby,
+ext/openssl. This is the successor of the version included in Ruby 2.3.
+
+Compatibility notes
+-------------------
+
+* Support for OpenSSL version 0.9.6 and 0.9.7 is completely removed. openssl gem
+  still works with OpenSSL 0.9.8, but users are strongly encouraged to upgrade
+  to at least 1.0.1, as OpenSSL < 1.0.1 will not receive any security fixes from
+  the OpenSSL development team.
+
+Supported platforms
+-------------------
+
+* OpenSSL 1.0.0, 1.0.1, 1.0.2, 1.1.0
+* OpenSSL < 0.9.8 is no longer supported.
+* LibreSSL 2.3, 2.4, 2.5
+* Ruby 2.3, 2.4
+
+Notable changes
+---------------
+
+* Add support for OpenSSL 1.1.0.
+  [[Feature #12324]](https://bugs.ruby-lang.org/issues/12324)
+* Add support for LibreSSL
+
+* OpenSSL::Cipher
+
+  - OpenSSL::Cipher#key= and #iv= reject too long inputs. They used to truncate
+    silently. [[Bug #12561]](https://bugs.ruby-lang.org/issues/12561)
+
+  - OpenSSL::Cipher#iv_len= is added. It allows changing IV (nonce) length if
+    using AEAD ciphers.
+    [[Bug #8667]](https://bugs.ruby-lang.org/issues/8667),
+    [[Bug #10420]](https://bugs.ruby-lang.org/issues/10420),
+    [[GH ruby/ruby#569]](https://github.com/ruby/ruby/pull/569),
+    [[GH ruby/openssl#58]](https://github.com/ruby/openssl/pull/58)
+
+  - OpenSSL::Cipher#auth_tag_len= is added. This sets the authentication tag
+    length to be generated by an AEAD cipher.
+
+* OpenSSL::OCSP
+
+  - Accessor methods are added to OpenSSL::OCSP::CertificateId.
+    [[Feature #7181]](https://bugs.ruby-lang.org/issues/7181)
+
+  - OpenSSL::OCSP::Request and BasicResponse can be signed with non-SHA-1 hash
+    algorithm. [[Feature #11552]](https://bugs.ruby-lang.org/issues/11552)
+
+  - OpenSSL::OCSP::CertificateId and BasicResponse can be encoded into DER.
+
+  - A new class OpenSSL::OCSP::SingleResponse is added for convenience.
+
+  - OpenSSL::OCSP::BasicResponse#add_status accepts absolute times. They used to
+    accept only relative seconds from the current time.
+
+* OpenSSL::PKey
+
+  - OpenSSL::PKey::EC follows the general PKey interface.
+    [[Bug #6567]](https://bugs.ruby-lang.org/issues/6567)
+
+  - OpenSSL::PKey.read raises OpenSSL::PKey::PKeyError instead of ArgumentError
+    for consistency with OpenSSL::PKey::{DH,DSA,RSA,EC}#new.
+    [[Bug #11774]](https://bugs.ruby-lang.org/issues/11774),
+    [[GH ruby/openssl#55]](https://github.com/ruby/openssl/pull/55)
+
+  - OpenSSL::PKey::EC::Group retrieved by OpenSSL::PKey::EC#group is no longer
+    linked with the EC key. Modifications to the EC::Group have no effect on the
+    key. [[GH ruby/openssl#71]](https://github.com/ruby/openssl/pull/71)
+
+  - OpenSSL::PKey::EC::Point#to_bn allows specifying the point conversion form
+    by the optional argument.
+
+* OpenSSL::SSL
+
+  - OpenSSL::SSL::SSLSocket#tmp_key is added. A client can call it after the
+    connection is established to retrieve the ephemeral key.
+    [[GH ruby/ruby#1318]](https://github.com/ruby/ruby/pull/1318)
+
+  - The automatic ephemeral ECDH curve selection is enabled by default when
+    built with OpenSSL >= 1.0.2 or LibreSSL.
+
+  - OpenSSL::SSL::SSLContext#security_level= is added. You can set the "security
+    level" of the SSL context. This is effective only when built with OpenSSL
+    1.1.0.
+
+  - A new option 'verify_hostname' is added to OpenSSL::SSL::SSLContext. When it
+    is enabled, and the SNI hostname is also set, the hostname verification on
+    the server certificate is automatically performed. It is now enabled by
+    OpenSSL::SSL::Context#set_params.
+    [[GH ruby/openssl#60]](https://github.com/ruby/openssl/pull/60)
+
+Removals
+--------
+
+* OpenSSL::Engine
+
+  - OpenSSL::Engine.cleanup does nothing when built with OpenSSL 1.1.0.
+
+* OpenSSL::SSL
+
+  - OpenSSL::PKey::DH::DEFAULT_512 is removed. Hence servers no longer use
+    512-bit DH group by default. It is considered too weak nowadays.
+    [[Bug #11968]](https://bugs.ruby-lang.org/issues/11968),
+    [[GH ruby/ruby#1196]](https://github.com/ruby/ruby/pull/1196)
+
+  - RC4 cipher suites are removed from OpenSSL::SSL::SSLContext::DEFAULT_PARAMS.
+    RC4 is now considered to be weak.
+    [[GH ruby/openssl#50]](https://github.com/ruby/openssl/pull/50)
+
+Deprecations
+------------
+
+* OpenSSL::PKey
+
+  - OpenSSL::PKey::RSA#n=, #e=, #d=, #p=, #q=, #dmp1=, #dmq1=, #iqmp=,
+    OpenSSL::PKey::DSA#p=, #q=, #g=, #priv_key=, #pub_key=,
+    OpenSSL::PKey::DH#p=, #g=, #priv_key= and #pub_key= are deprecated. They are
+    disabled when built with OpenSSL 1.1.0, due to its API change. Instead,
+    OpenSSL::PKey::RSA#set_key, #set_factors, #set_crt_params,
+    OpenSSL::PKey::DSA#set_pqg, #set_key, OpenSSL::PKey::DH#set_pqg and #set_key
+    are added.
+
+* OpenSSL::Random
+
+  - OpenSSL::Random.pseudo_bytes is deprecated, and not defined when built with
+    OpenSSL 1.1.0. Use OpenSSL::Random.random_bytes instead.
+
+* OpenSSL::SSL
+
+  - OpenSSL::SSL::SSLContext#tmp_ecdh_callback is deprecated, as the underlying
+    API SSL_CTX_set_tmp_ecdh_callback() is removed in OpenSSL 1.1.0. It was
+    first added in Ruby 2.3.0. To specify the curve to be used in ephemeral
+    ECDH, use OpenSSL::SSL::SSLContext#ecdh_curves=. The automatic curve
+    selection is also now enabled by default when built with a capable OpenSSL.
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..426810a
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,56 @@
+Ruby is copyrighted free software by Yukihiro Matsumoto <matz at netlab.jp>.
+You can redistribute it and/or modify it under either the terms of the
+2-clause BSDL (see the file BSDL), or the conditions below:
+
+  1. You may make and give away verbatim copies of the source form of the
+     software without restriction, provided that you duplicate all of the
+     original copyright notices and associated disclaimers.
+
+  2. You may modify your copy of the software in any way, provided that
+     you do at least ONE of the following:
+
+       a) place your modifications in the Public Domain or otherwise
+          make them Freely Available, such as by posting said
+	  modifications to Usenet or an equivalent medium, or by allowing
+	  the author to include your modifications in the software.
+
+       b) use the modified software only within your corporation or
+          organization.
+
+       c) give non-standard binaries non-standard names, with
+          instructions on where to get the original software distribution.
+
+       d) make other distribution arrangements with the author.
+
+  3. You may distribute the software in object code or binary form,
+     provided that you do at least ONE of the following:
+
+       a) distribute the binaries and library files of the software,
+	  together with instructions (in the manual page or equivalent)
+	  on where to get the original distribution.
+
+       b) accompany the distribution with the machine-readable source of
+	  the software.
+
+       c) give non-standard binaries non-standard names, with
+          instructions on where to get the original software distribution.
+
+       d) make other distribution arrangements with the author.
+
+  4. You may modify and include the part of the software into any other
+     software (possibly commercial).  But some files in the distribution
+     are not written by the author, so that they are not under these terms.
+
+     For the list of those files and their copying conditions, see the
+     file LEGAL.
+
+  5. The scripts and library files supplied as input to or produced as
+     output from the software do not automatically fall under the
+     copyright of the software, but belong to whomever generated them,
+     and may be sold commercially, and may be aggregated with this
+     software.
+
+  6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
+     IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+     WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+     PURPOSE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..21d0f52
--- /dev/null
+++ b/README.md
@@ -0,0 +1,66 @@
+# OpenSSL for Ruby
+
+[![Build Status](https://travis-ci.org/ruby/openssl.svg?branch=master)](https://travis-ci.org/ruby/openssl)
+[![Build status](https://ci.appveyor.com/api/projects/status/b8djtmwo7l26f88y/branch/master?svg=true)](https://ci.appveyor.com/project/ruby/openssl/branch/master)
+
+OpenSSL provides SSL, TLS and general purpose cryptography. It wraps the
+OpenSSL library.
+
+## Installation
+
+The openssl gem is available at [rubygems.org](https://rubygems.org/gems/openssl).
+You can install with:
+
+```
+gem install openssl
+```
+
+You may need to specify the path where OpenSSL is installed.
+
+```
+gem install openssl -- --with-openssl-dir=/opt/openssl
+```
+
+Alternatively, you can install the gem with `bundler`:
+
+```ruby
+# Gemfile
+gem 'openssl'
+# or specify git master
+gem 'openssl', github: 'ruby/openssl'
+```
+
+After doing `bundle install`, you should have the gem installed in your bundle.
+
+## Usage
+
+Once installed, you can require "openssl" in your application.
+
+```ruby
+require "openssl"
+```
+
+**NOTE**: If you are using Ruby 2.3 (and not Bundler), you **must** activate
+the gem version of openssl, otherwise the default gem packaged with the Ruby
+installation will be used:
+
+```ruby
+gem "openssl"
+require "openssl"
+```
+
+## Documentation
+
+See https://ruby.github.io/openssl/.
+
+## Contributing
+
+Please read our [CONTRIBUTING.md] for instructions.
+
+## Security
+
+Security issues should be reported to ruby-core by following the process
+described on ["Security at ruby-lang.org"](https://www.ruby-lang.org/en/security/).
+
+
+[CONTRIBUTING.md]: https://github.com/ruby/openssl/tree/master/CONTRIBUTING.md
diff --git a/Rakefile b/Rakefile
new file mode 100644
index 0000000..2fca743
--- /dev/null
+++ b/Rakefile
@@ -0,0 +1,73 @@
+require 'rake'
+require 'rake/testtask'
+require 'rdoc/task'
+
+begin
+  require 'rake/extensiontask'
+  Rake::ExtensionTask.new('openssl')
+rescue LoadError
+  warn "rake-compiler not installed. Run 'rake install_dependencies' to " \
+    "install testing dependency gems."
+end
+
+Rake::TestTask.new do |t|
+  t.libs << 'test'
+  t.warning = true
+end
+
+RDoc::Task.new do |rdoc|
+  rdoc.main = "README.md"
+  rdoc.rdoc_files.include("*.md", "lib/**/*.rb", "ext/**/*.c")
+end
+
+task :test => :debug
+task :debug do
+  ruby "-I./lib -ropenssl -ve'puts OpenSSL::OPENSSL_VERSION, OpenSSL::OPENSSL_LIBRARY_VERSION'"
+end
+
+task :install_dependencies do
+  if ENV["USE_HTTP_RUBYGEMS_ORG"] == "1"
+    Gem.sources.replace([Gem::Source.new("http://rubygems.org")])
+  end
+
+  Gem.configuration.verbose = false
+  gemspec = eval(File.read("openssl.gemspec"))
+  gemspec.development_dependencies.each do |dep|
+    print "Installing #{dep.name} (#{dep.requirement}) ... "
+    gem = Gem.install(dep.name, dep.requirement, force: true)
+    puts "#{gem[0].version}"
+  end
+end
+
+namespace :sync do
+  task :from_ruby do
+    sh "./tool/sync-with-trunk"
+  end
+
+  task :to_ruby do
+    trunk_path = ENV.fetch("RUBY_TRUNK_PATH", "../ruby")
+
+    rsync = "rsync -av --delete"
+    excludes = %w{Makefile extconf.h mkmf.log depend *.o *.so *.bundle}
+    excludes.each { |name| rsync << " --exclude #{name}" }
+
+    paths = [
+      ["ext/openssl/", "ext/openssl/"],
+      ["test/utils.rb", "test/openssl/"],
+      ["test/ut_eof.rb", "test/openssl/"],
+      ["test/test_*", "test/openssl/"],
+      ["lib/", "ext/openssl/lib/"],
+      ["sample/", "sample/openssl/"],
+      ["History.md", "ext/openssl/"],
+    ]
+    paths.each do |src, dst|
+      sh "#{rsync} #{src} #{trunk_path}/#{dst}"
+    end
+
+    gemspec_file = File.expand_path("../openssl.gemspec", __FILE__)
+    gemspec = eval(File.read(gemspec_file), binding, gemspec_file)
+    File.write("#{trunk_path}/ext/openssl/openssl.gemspec", gemspec.to_ruby)
+
+    puts "Don't forget to update ext/openssl/depend"
+  end
+end
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 0000000..bd72ced
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,16 @@
+---
+clone_depth: 10
+install:
+  - SET PATH=C:\Ruby%ruby_version%\bin;%PATH%
+  - appveyor DownloadFile http://dl.bintray.com/oneclick/OpenKnapsack/x64/openssl-1.0.2j-x64-windows.tar.lzma
+  - 7z e openssl-1.0.2j-x64-windows.tar.lzma
+  - 7z x -y -oC:\Ruby%ruby_version% openssl-1.0.2j-x64-windows.tar
+  - ruby -S rake install_dependencies
+build_script:
+  - rake -rdevkit compile -- --with-openssl-dir=C:\Ruby%ruby_version%
+test_script:
+  - rake test
+deploy: off
+environment:
+  matrix:
+    - ruby_version: "23-x64"
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..4289654
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,13 @@
+compile: &defaults
+  build: .
+  environment:
+    RUBY_VERSION:
+    OPENSSL_VERSION:
+    MDEBUG:
+  command: rake compile
+test:
+  <<: *defaults
+  command: rake compile test
+debug:
+  <<: *defaults
+  command: /bin/bash
diff --git a/ext/openssl/deprecation.rb b/ext/openssl/deprecation.rb
new file mode 100644
index 0000000..0c3ab62
--- /dev/null
+++ b/ext/openssl/deprecation.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: false
+module OpenSSL
+  def self.deprecated_warning_flag
+    unless flag = (@deprecated_warning_flag ||= nil)
+      if try_compile("", flag = "-Werror=deprecated-declarations")
+        if /darwin/ =~ RUBY_PLATFORM and with_config("broken-apple-openssl")
+          flag = "-Wno-deprecated-declarations"
+        end
+        $warnflags << " #{flag}"
+      else
+        flag = ""
+      end
+      @deprecated_warning_flag = flag
+    end
+    flag
+  end
+
+  def self.check_func(func, header)
+    have_func(func, header, deprecated_warning_flag)
+  end
+
+  def self.check_func_or_macro(func, header)
+    check_func(func, header) or
+      have_macro(func, header) && $defs.push("-DHAVE_#{func.upcase}")
+  end
+end
diff --git a/ext/openssl/extconf.rb b/ext/openssl/extconf.rb
new file mode 100644
index 0000000..8f60470
--- /dev/null
+++ b/ext/openssl/extconf.rb
@@ -0,0 +1,201 @@
+# -*- coding: us-ascii -*-
+# frozen_string_literal: false
+=begin
+= Info
+  'OpenSSL for Ruby 2' project
+  Copyright (C) 2002  Michal Rokos <m.rokos at sh.cvut.cz>
+  All rights reserved.
+
+= Licence
+  This program is licensed under the same licence as Ruby.
+  (See the file 'LICENCE'.)
+=end
+
+require "mkmf"
+require File.expand_path('../deprecation', __FILE__)
+
+dir_config("openssl")
+dir_config("kerberos")
+
+Logging::message "=== OpenSSL for Ruby configurator ===\n"
+
+# Add -Werror=deprecated-declarations to $warnflags if available
+OpenSSL.deprecated_warning_flag
+
+##
+# Adds -DOSSL_DEBUG for compilation and some more targets when GCC is used
+# To turn it on, use: --with-debug or --enable-debug
+#
+if with_config("debug") or enable_config("debug")
+  $defs.push("-DOSSL_DEBUG")
+end
+
+Logging::message "=== Checking for system dependent stuff... ===\n"
+have_library("nsl", "t_open")
+have_library("socket", "socket")
+
+Logging::message "=== Checking for required stuff... ===\n"
+result = pkg_config("openssl") && have_header("openssl/ssl.h")
+
+def find_openssl_library
+  if $mswin || $mingw
+    # required for static OpenSSL libraries
+    have_library("gdi32") # OpenSSL <= 1.0.2 (for RAND_screen())
+    have_library("crypt32")
+  end
+
+  return false unless have_header("openssl/ssl.h")
+
+  ret = have_library("crypto", "CRYPTO_malloc") &&
+    have_library("ssl", "SSL_new")
+  return ret if ret
+
+  if $mswin
+    # OpenSSL >= 1.1.0: libcrypto.lib and libssl.lib.
+    if have_library("libcrypto", "CRYPTO_malloc") &&
+        have_library("libssl", "SSL_new")
+      return true
+    end
+
+    # OpenSSL <= 1.0.2: libeay32.lib and ssleay32.lib.
+    if have_library("libeay32", "CRYPTO_malloc") &&
+        have_library("ssleay32", "SSL_new")
+      return true
+    end
+
+    # LibreSSL: libcrypto-##.lib and libssl-##.lib, where ## is the ABI version
+    # number. We have to find the version number out by scanning libpath.
+    libpath = $LIBPATH.dup
+    libpath |= ENV["LIB"].split(File::PATH_SEPARATOR)
+    libpath.map! { |d| d.tr(File::ALT_SEPARATOR, File::SEPARATOR) }
+
+    ret = [
+      ["crypto", "CRYPTO_malloc"],
+      ["ssl", "SSL_new"]
+    ].all? do |base, func|
+      result = false
+      libs = ["lib#{base}-[0-9][0-9]", "lib#{base}-[0-9][0-9][0-9]"]
+      libs = Dir.glob(libs.map{|l| libpath.map{|d| File.join(d, l + ".*")}}.flatten).map{|path| File.basename(path, ".*")}.uniq
+      libs.each do |lib|
+        result = have_library(lib, func)
+        break if result
+      end
+      result
+    end
+    return ret if ret
+  end
+  return false
+end
+
+unless result
+  unless find_openssl_library
+    Logging::message "=== Checking for required stuff failed. ===\n"
+    Logging::message "Makefile wasn't created. Fix the errors above.\n"
+    exit 1
+  end
+end
+
+result = checking_for("OpenSSL version is 0.9.8 or later") {
+  try_static_assert("OPENSSL_VERSION_NUMBER >= 0x00908000L", "openssl/opensslv.h")
+}
+unless result
+  raise "OpenSSL 0.9.8 or later required."
+end
+
+if /darwin/ =~ RUBY_PLATFORM and !OpenSSL.check_func("SSL_library_init()", "openssl/ssl.h")
+  raise "Ignore OpenSSL broken by Apple.\nPlease use another openssl. (e.g. using `configure --with-openssl-dir=/path/to/openssl')"
+end
+
+Logging::message "=== Checking for OpenSSL features... ===\n"
+# compile options
+
+# check OPENSSL_NO_{SSL2,SSL3_METHOD} macro: on some environment, these symbols
+# exist even if compiled with no-ssl2 or no-ssl3-method.
+unless have_macro("OPENSSL_NO_SSL2", "openssl/opensslconf.h")
+  have_func("SSLv2_method")
+end
+unless have_macro("OPENSSL_NO_SSL3_METHOD", "openssl/opensslconf.h")
+  have_func("SSLv3_method")
+end
+have_func("TLSv1_1_method")
+have_func("TLSv1_2_method")
+have_func("RAND_egd")
+engines = %w{builtin_engines openbsd_dev_crypto dynamic 4758cca aep atalla chil
+             cswift nuron sureware ubsec padlock capi gmp gost cryptodev aesni}
+engines.each { |name|
+  OpenSSL.check_func_or_macro("ENGINE_load_#{name}", "openssl/engine.h")
+}
+
+# added in 0.9.8X
+have_func("EVP_CIPHER_CTX_new")
+have_func("EVP_CIPHER_CTX_free")
+OpenSSL.check_func_or_macro("SSL_CTX_clear_options", "openssl/ssl.h")
+
+# added in 1.0.0
+have_func("ASN1_TIME_adj")
+have_func("EVP_CIPHER_CTX_copy")
+have_func("EVP_PKEY_base_id")
+have_func("HMAC_CTX_copy")
+have_func("PKCS5_PBKDF2_HMAC")
+have_func("X509_NAME_hash_old")
+have_func("X509_STORE_CTX_get0_current_crl")
+have_func("X509_STORE_set_verify_cb")
+have_func("i2d_ASN1_SET_ANY")
+have_func("SSL_SESSION_cmp") # removed
+OpenSSL.check_func_or_macro("SSL_set_tlsext_host_name", "openssl/ssl.h")
+have_struct_member("CRYPTO_THREADID", "ptr", "openssl/crypto.h")
+have_func("EVP_PKEY_get0")
+
+# added in 1.0.1
+have_func("SSL_CTX_set_next_proto_select_cb")
+have_macro("EVP_CTRL_GCM_GET_TAG", ['openssl/evp.h']) && $defs.push("-DHAVE_AUTHENTICATED_ENCRYPTION")
+
+# added in 1.0.2
+have_func("EC_curve_nist2nid")
+have_func("X509_REVOKED_dup")
+have_func("X509_STORE_CTX_get0_store")
+have_func("SSL_CTX_set_alpn_select_cb")
+OpenSSL.check_func_or_macro("SSL_CTX_set1_curves_list", "openssl/ssl.h")
+OpenSSL.check_func_or_macro("SSL_CTX_set_ecdh_auto", "openssl/ssl.h")
+OpenSSL.check_func_or_macro("SSL_get_server_tmp_key", "openssl/ssl.h")
+have_func("SSL_is_server")
+
+# added in 1.1.0
+have_func("CRYPTO_lock") || $defs.push("-DHAVE_OPENSSL_110_THREADING_API")
+have_struct_member("SSL", "ctx", "openssl/ssl.h") || $defs.push("-DHAVE_OPAQUE_OPENSSL")
+have_func("BN_GENCB_new")
+have_func("BN_GENCB_free")
+have_func("BN_GENCB_get_arg")
+have_func("EVP_MD_CTX_new")
+have_func("EVP_MD_CTX_free")
+have_func("HMAC_CTX_new")
+have_func("HMAC_CTX_free")
+OpenSSL.check_func("RAND_pseudo_bytes", "openssl/rand.h") # deprecated
+have_func("X509_STORE_get_ex_data")
+have_func("X509_STORE_set_ex_data")
+have_func("X509_CRL_get0_signature")
+have_func("X509_REQ_get0_signature")
+have_func("X509_REVOKED_get0_serialNumber")
+have_func("X509_REVOKED_get0_revocationDate")
+have_func("X509_get0_tbs_sigalg")
+have_func("X509_STORE_CTX_get0_untrusted")
+have_func("X509_STORE_CTX_get0_cert")
+have_func("X509_STORE_CTX_get0_chain")
+have_func("OCSP_SINGLERESP_get0_id")
+have_func("SSL_CTX_get_ciphers")
+have_func("X509_up_ref")
+have_func("X509_CRL_up_ref")
+have_func("X509_STORE_up_ref")
+have_func("SSL_SESSION_up_ref")
+have_func("EVP_PKEY_up_ref")
+OpenSSL.check_func_or_macro("SSL_CTX_set_tmp_ecdh_callback", "openssl/ssl.h") # removed
+OpenSSL.check_func_or_macro("SSL_CTX_set_min_proto_version", "openssl/ssl.h")
+have_func("SSL_CTX_get_security_level")
+have_func("X509_get0_notBefore")
+have_func("SSL_SESSION_get_protocol_version")
+
+Logging::message "=== Checking done. ===\n"
+
+create_header
+create_makefile("openssl")
+Logging::message "Done.\n"
diff --git a/ext/openssl/openssl_missing.c b/ext/openssl/openssl_missing.c
new file mode 100644
index 0000000..94ce85a
--- /dev/null
+++ b/ext/openssl/openssl_missing.c
@@ -0,0 +1,173 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2001-2002  Michal Rokos <m.rokos at sh.cvut.cz>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#include RUBY_EXTCONF_H
+
+#include <string.h> /* memcpy() */
+#if !defined(OPENSSL_NO_ENGINE)
+# include <openssl/engine.h>
+#endif
+#if !defined(OPENSSL_NO_HMAC)
+# include <openssl/hmac.h>
+#endif
+#include <openssl/x509_vfy.h>
+
+#include "openssl_missing.h"
+
+/* added in 0.9.8X */
+#if !defined(HAVE_EVP_CIPHER_CTX_NEW)
+EVP_CIPHER_CTX *
+ossl_EVP_CIPHER_CTX_new(void)
+{
+    EVP_CIPHER_CTX *ctx = OPENSSL_malloc(sizeof(EVP_CIPHER_CTX));
+    if (!ctx)
+	return NULL;
+    EVP_CIPHER_CTX_init(ctx);
+    return ctx;
+}
+#endif
+
+#if !defined(HAVE_EVP_CIPHER_CTX_FREE)
+void
+ossl_EVP_CIPHER_CTX_free(EVP_CIPHER_CTX *ctx)
+{
+    if (ctx) {
+	EVP_CIPHER_CTX_cleanup(ctx);
+	OPENSSL_free(ctx);
+    }
+}
+#endif
+
+/* added in 1.0.0 */
+#if !defined(HAVE_EVP_CIPHER_CTX_COPY)
+/*
+ * this function does not exist in OpenSSL yet... or ever?.
+ * a future version may break this function.
+ * tested on 0.9.7d.
+ */
+int
+ossl_EVP_CIPHER_CTX_copy(EVP_CIPHER_CTX *out, const EVP_CIPHER_CTX *in)
+{
+    memcpy(out, in, sizeof(EVP_CIPHER_CTX));
+
+#if !defined(OPENSSL_NO_ENGINE)
+    if (in->engine) ENGINE_add(out->engine);
+    if (in->cipher_data) {
+	out->cipher_data = OPENSSL_malloc(in->cipher->ctx_size);
+	memcpy(out->cipher_data, in->cipher_data, in->cipher->ctx_size);
+    }
+#endif
+
+    return 1;
+}
+#endif
+
+#if !defined(OPENSSL_NO_HMAC)
+#if !defined(HAVE_HMAC_CTX_COPY)
+int
+ossl_HMAC_CTX_copy(HMAC_CTX *out, HMAC_CTX *in)
+{
+    if (!out || !in)
+	return 0;
+
+    memcpy(out, in, sizeof(HMAC_CTX));
+
+    EVP_MD_CTX_copy(&out->md_ctx, &in->md_ctx);
+    EVP_MD_CTX_copy(&out->i_ctx, &in->i_ctx);
+    EVP_MD_CTX_copy(&out->o_ctx, &in->o_ctx);
+
+    return 1;
+}
+#endif /* HAVE_HMAC_CTX_COPY */
+#endif /* NO_HMAC */
+
+/* added in 1.0.2 */
+#if !defined(OPENSSL_NO_EC)
+#if !defined(HAVE_EC_CURVE_NIST2NID)
+static struct {
+    const char *name;
+    int nid;
+} nist_curves[] = {
+    {"B-163", NID_sect163r2},
+    {"B-233", NID_sect233r1},
+    {"B-283", NID_sect283r1},
+    {"B-409", NID_sect409r1},
+    {"B-571", NID_sect571r1},
+    {"K-163", NID_sect163k1},
+    {"K-233", NID_sect233k1},
+    {"K-283", NID_sect283k1},
+    {"K-409", NID_sect409k1},
+    {"K-571", NID_sect571k1},
+    {"P-192", NID_X9_62_prime192v1},
+    {"P-224", NID_secp224r1},
+    {"P-256", NID_X9_62_prime256v1},
+    {"P-384", NID_secp384r1},
+    {"P-521", NID_secp521r1}
+};
+
+int
+ossl_EC_curve_nist2nid(const char *name)
+{
+    size_t i;
+    for (i = 0; i < (sizeof(nist_curves) / sizeof(nist_curves[0])); i++) {
+	if (!strcmp(nist_curves[i].name, name))
+	    return nist_curves[i].nid;
+    }
+    return NID_undef;
+}
+#endif
+#endif
+
+/*** added in 1.1.0 ***/
+#if !defined(HAVE_HMAC_CTX_NEW)
+HMAC_CTX *
+ossl_HMAC_CTX_new(void)
+{
+    HMAC_CTX *ctx = OPENSSL_malloc(sizeof(HMAC_CTX));
+    if (!ctx)
+	return NULL;
+    HMAC_CTX_init(ctx);
+    return ctx;
+}
+#endif
+
+#if !defined(HAVE_HMAC_CTX_FREE)
+void
+ossl_HMAC_CTX_free(HMAC_CTX *ctx)
+{
+    if (ctx) {
+	HMAC_CTX_cleanup(ctx);
+	OPENSSL_free(ctx);
+    }
+}
+#endif
+
+#if !defined(HAVE_X509_CRL_GET0_SIGNATURE)
+void
+ossl_X509_CRL_get0_signature(const X509_CRL *crl, const ASN1_BIT_STRING **psig,
+			     const X509_ALGOR **palg)
+{
+    if (psig != NULL)
+	*psig = crl->signature;
+    if (palg != NULL)
+	*palg = crl->sig_alg;
+}
+#endif
+
+#if !defined(HAVE_X509_REQ_GET0_SIGNATURE)
+void
+ossl_X509_REQ_get0_signature(const X509_REQ *req, const ASN1_BIT_STRING **psig,
+			     const X509_ALGOR **palg)
+{
+    if (psig != NULL)
+	*psig = req->signature;
+    if (palg != NULL)
+	*palg = req->sig_alg;
+}
+#endif
diff --git a/ext/openssl/openssl_missing.h b/ext/openssl/openssl_missing.h
new file mode 100644
index 0000000..3d11aec
--- /dev/null
+++ b/ext/openssl/openssl_missing.h
@@ -0,0 +1,265 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2001-2002  Michal Rokos <m.rokos at sh.cvut.cz>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#if !defined(_OSSL_OPENSSL_MISSING_H_)
+#define _OSSL_OPENSSL_MISSING_H_
+
+#include "ruby/config.h"
+
+/* added in 0.9.8X */
+#if !defined(HAVE_EVP_CIPHER_CTX_NEW)
+EVP_CIPHER_CTX *ossl_EVP_CIPHER_CTX_new(void);
+#  define EVP_CIPHER_CTX_new ossl_EVP_CIPHER_CTX_new
+#endif
+
+#if !defined(HAVE_EVP_CIPHER_CTX_FREE)
+void ossl_EVP_CIPHER_CTX_free(EVP_CIPHER_CTX *);
+#  define EVP_CIPHER_CTX_free ossl_EVP_CIPHER_CTX_free
+#endif
+
+#if !defined(HAVE_SSL_CTX_CLEAR_OPTIONS)
+#  define SSL_CTX_clear_options(ctx, op) ((ctx)->options &= ~(op))
+#endif
+
+/* added in 1.0.0 */
+#if !defined(HAVE_EVP_PKEY_BASE_ID)
+#  define EVP_PKEY_base_id(pkey) EVP_PKEY_type((pkey)->type)
+#endif
+
+#if !defined(HAVE_EVP_CIPHER_CTX_COPY)
+int ossl_EVP_CIPHER_CTX_copy(EVP_CIPHER_CTX *, const EVP_CIPHER_CTX *);
+#  define EVP_CIPHER_CTX_copy ossl_EVP_CIPHER_CTX_copy
+#endif
+
+#if !defined(HAVE_HMAC_CTX_COPY)
+int ossl_HMAC_CTX_copy(HMAC_CTX *out, HMAC_CTX *in);
+#  define HMAC_CTX_copy ossl_HMAC_CTX_copy
+#endif
+
+#if !defined(HAVE_X509_STORE_CTX_GET0_CURRENT_CRL)
+#  define X509_STORE_CTX_get0_current_crl(x) ((x)->current_crl)
+#endif
+
+#if !defined(HAVE_X509_STORE_SET_VERIFY_CB)
+#  define X509_STORE_set_verify_cb X509_STORE_set_verify_cb_func
+#endif
+
+#if !defined(HAVE_I2D_ASN1_SET_ANY)
+#  define i2d_ASN1_SET_ANY(sk, x) i2d_ASN1_SET_OF_ASN1_TYPE((sk), (x), \
+		i2d_ASN1_TYPE, V_ASN1_SET, V_ASN1_UNIVERSAL, 0)
+#endif
+
+#if !defined(HAVE_EVP_PKEY_GET0)
+#  define EVP_PKEY_get0(pk) (pk->pkey.ptr)
+#endif
+
+/* added in 1.0.2 */
+#if !defined(OPENSSL_NO_EC)
+#if !defined(HAVE_EC_CURVE_NIST2NID)
+int ossl_EC_curve_nist2nid(const char *);
+#  define EC_curve_nist2nid ossl_EC_curve_nist2nid
+#endif
+#endif
+
+#if !defined(HAVE_X509_REVOKED_DUP)
+# define X509_REVOKED_dup(rev) (X509_REVOKED *)ASN1_dup((i2d_of_void *)i2d_X509_REVOKED, \
+	(d2i_of_void *)d2i_X509_REVOKED, (char *)(rev))
+#endif
+
+#if !defined(HAVE_X509_STORE_CTX_GET0_STORE)
+#  define X509_STORE_CTX_get0_store(x) ((x)->ctx)
+#endif
+
+#if !defined(HAVE_SSL_IS_SERVER)
+#  define SSL_is_server(s) ((s)->server)
+#endif
+
+/* added in 1.1.0 */
+#if !defined(HAVE_BN_GENCB_NEW)
+#  define BN_GENCB_new() ((BN_GENCB *)OPENSSL_malloc(sizeof(BN_GENCB)))
+#endif
+
+#if !defined(HAVE_BN_GENCB_FREE)
+#  define BN_GENCB_free(cb) OPENSSL_free(cb)
+#endif
+
+#if !defined(HAVE_BN_GENCB_GET_ARG)
+#  define BN_GENCB_get_arg(cb) (cb)->arg
+#endif
+
+#if !defined(HAVE_EVP_MD_CTX_NEW)
+#  define EVP_MD_CTX_new EVP_MD_CTX_create
+#endif
+
+#if !defined(HAVE_EVP_MD_CTX_FREE)
+#  define EVP_MD_CTX_free EVP_MD_CTX_destroy
+#endif
+
+#if !defined(HAVE_HMAC_CTX_NEW)
+HMAC_CTX *ossl_HMAC_CTX_new(void);
+#  define HMAC_CTX_new ossl_HMAC_CTX_new
+#endif
+
+#if !defined(HAVE_HMAC_CTX_FREE)
+void ossl_HMAC_CTX_free(HMAC_CTX *);
+#  define HMAC_CTX_free ossl_HMAC_CTX_free
+#endif
+
+#if !defined(HAVE_X509_STORE_GET_EX_DATA)
+#  define X509_STORE_get_ex_data(x, idx) \
+	CRYPTO_get_ex_data(&(x)->ex_data, (idx))
+#endif
+
+#if !defined(HAVE_X509_STORE_SET_EX_DATA)
+#  define X509_STORE_set_ex_data(x, idx, data) \
+	CRYPTO_set_ex_data(&(x)->ex_data, (idx), (data))
+#  define X509_STORE_get_ex_new_index(l, p, newf, dupf, freef) \
+	CRYPTO_get_ex_new_index(CRYPTO_EX_INDEX_X509_STORE, (l), (p), \
+				(newf), (dupf), (freef))
+#endif
+
+#if !defined(HAVE_X509_CRL_GET0_SIGNATURE)
+void ossl_X509_CRL_get0_signature(const X509_CRL *, const ASN1_BIT_STRING **, const X509_ALGOR **);
+#  define X509_CRL_get0_signature ossl_X509_CRL_get0_signature
+#endif
+
+#if !defined(HAVE_X509_REQ_GET0_SIGNATURE)
+void ossl_X509_REQ_get0_signature(const X509_REQ *, const ASN1_BIT_STRING **, const X509_ALGOR **);
+#  define X509_REQ_get0_signature ossl_X509_REQ_get0_signature
+#endif
+
+#if !defined(HAVE_X509_REVOKED_GET0_SERIALNUMBER)
+#  define X509_REVOKED_get0_serialNumber(x) ((x)->serialNumber)
+#endif
+
+#if !defined(HAVE_X509_REVOKED_GET0_REVOCATIONDATE)
+#  define X509_REVOKED_get0_revocationDate(x) ((x)->revocationDate)
+#endif
+
+#if !defined(HAVE_X509_GET0_TBS_SIGALG)
+#  define X509_get0_tbs_sigalg(x) ((x)->cert_info->signature)
+#endif
+
+#if !defined(HAVE_X509_STORE_CTX_GET0_UNTRUSTED)
+#  define X509_STORE_CTX_get0_untrusted(x) ((x)->untrusted)
+#endif
+
+#if !defined(HAVE_X509_STORE_CTX_GET0_CERT)
+#  define X509_STORE_CTX_get0_cert(x) ((x)->cert)
+#endif
+
+#if !defined(HAVE_X509_STORE_CTX_GET0_CHAIN)
+#  define X509_STORE_CTX_get0_chain(ctx) X509_STORE_CTX_get_chain(ctx)
+#endif
+
+#if !defined(HAVE_OCSP_SINGLERESP_GET0_ID)
+#  define OCSP_SINGLERESP_get0_id(s) ((s)->certId)
+#endif
+
+#if !defined(HAVE_SSL_CTX_GET_CIPHERS)
+#  define SSL_CTX_get_ciphers(ctx) ((ctx)->cipher_list)
+#endif
+
+#if !defined(HAVE_X509_UP_REF)
+#  define X509_up_ref(x) \
+	CRYPTO_add(&(x)->references, 1, CRYPTO_LOCK_X509)
+#endif
+
+#if !defined(HAVE_X509_CRL_UP_REF)
+#  define X509_CRL_up_ref(x) \
+	CRYPTO_add(&(x)->references, 1, CRYPTO_LOCK_X509_CRL);
+#endif
+
+#if !defined(HAVE_X509_STORE_UP_REF)
+#  define X509_STORE_up_ref(x) \
+	CRYPTO_add(&(x)->references, 1, CRYPTO_LOCK_X509_STORE);
+#endif
+
+#if !defined(HAVE_SSL_SESSION_UP_REF)
+#  define SSL_SESSION_up_ref(x) \
+	CRYPTO_add(&(x)->references, 1, CRYPTO_LOCK_SSL_SESSION);
+#endif
+
+#if !defined(HAVE_EVP_PKEY_UP_REF)
+#  define EVP_PKEY_up_ref(x) \
+	CRYPTO_add(&(x)->references, 1, CRYPTO_LOCK_EVP_PKEY);
+#endif
+
+#if !defined(HAVE_OPAQUE_OPENSSL)
+#define IMPL_PKEY_GETTER(_type, _name) \
+static inline _type *EVP_PKEY_get0_##_type(EVP_PKEY *pkey) { \
+	return pkey->pkey._name; }
+#define IMPL_KEY_ACCESSOR2(_type, _group, a1, a2, _fail_cond) \
+static inline void _type##_get0_##_group(_type *obj, const BIGNUM **a1, const BIGNUM **a2) { \
+	if (a1) *a1 = obj->a1; \
+	if (a2) *a2 = obj->a2; } \
+static inline int _type##_set0_##_group(_type *obj, BIGNUM *a1, BIGNUM *a2) { \
+	if (_fail_cond) return 0; \
+	BN_clear_free(obj->a1); obj->a1 = a1; \
+	BN_clear_free(obj->a2); obj->a2 = a2; \
+	return 1; }
+#define IMPL_KEY_ACCESSOR3(_type, _group, a1, a2, a3, _fail_cond) \
+static inline void _type##_get0_##_group(_type *obj, const BIGNUM **a1, const BIGNUM **a2, const BIGNUM **a3) { \
+	if (a1) *a1 = obj->a1; \
+	if (a2) *a2 = obj->a2; \
+	if (a3) *a3 = obj->a3; } \
+static inline int _type##_set0_##_group(_type *obj, BIGNUM *a1, BIGNUM *a2, BIGNUM *a3) { \
+	if (_fail_cond) return 0; \
+	BN_clear_free(obj->a1); obj->a1 = a1; \
+	BN_clear_free(obj->a2); obj->a2 = a2; \
+	BN_clear_free(obj->a3); obj->a3 = a3; \
+	return 1; }
+
+#if !defined(OPENSSL_NO_RSA)
+IMPL_PKEY_GETTER(RSA, rsa)
+IMPL_KEY_ACCESSOR3(RSA, key, n, e, d, (n == obj->n || e == obj->e || (obj->d && d == obj->d)))
+IMPL_KEY_ACCESSOR2(RSA, factors, p, q, (p == obj->p || q == obj->q))
+IMPL_KEY_ACCESSOR3(RSA, crt_params, dmp1, dmq1, iqmp, (dmp1 == obj->dmp1 || dmq1 == obj->dmq1 || iqmp == obj->iqmp))
+#endif
+
+#if !defined(OPENSSL_NO_DSA)
+IMPL_PKEY_GETTER(DSA, dsa)
+IMPL_KEY_ACCESSOR2(DSA, key, pub_key, priv_key, (pub_key == obj->pub_key || (obj->priv_key && priv_key == obj->priv_key)))
+IMPL_KEY_ACCESSOR3(DSA, pqg, p, q, g, (p == obj->p || q == obj->q || g == obj->g))
+#endif
+
+#if !defined(OPENSSL_NO_DH)
+IMPL_PKEY_GETTER(DH, dh)
+IMPL_KEY_ACCESSOR2(DH, key, pub_key, priv_key, (pub_key == obj->pub_key || (obj->priv_key && priv_key == obj->priv_key)))
+IMPL_KEY_ACCESSOR3(DH, pqg, p, q, g, (p == obj->p || obj->q && q == obj->q || g == obj->g))
+static inline ENGINE *DH_get0_engine(DH *dh) { return dh->engine; }
+#endif
+
+#if !defined(OPENSSL_NO_EC)
+IMPL_PKEY_GETTER(EC_KEY, ec)
+#endif
+
+#undef IMPL_PKEY_GETTER
+#undef IMPL_KEY_ACCESSOR2
+#undef IMPL_KEY_ACCESSOR3
+#endif /* HAVE_OPAQUE_OPENSSL */
+
+#if defined(HAVE_AUTHENTICATED_ENCRYPTION) && !defined(EVP_CTRL_AEAD_GET_TAG)
+#  define EVP_CTRL_AEAD_GET_TAG EVP_CTRL_GCM_GET_TAG
+#  define EVP_CTRL_AEAD_SET_TAG EVP_CTRL_GCM_SET_TAG
+#  define EVP_CTRL_AEAD_SET_IVLEN EVP_CTRL_GCM_SET_IVLEN
+#endif
+
+#if !defined(HAVE_X509_GET0_NOTBEFORE)
+#  define X509_get0_notBefore(x) X509_get_notBefore(x)
+#  define X509_get0_notAfter(x) X509_get_notAfter(x)
+#  define X509_CRL_get0_lastUpdate(x) X509_CRL_get_lastUpdate(x)
+#  define X509_CRL_get0_nextUpdate(x) X509_CRL_get_nextUpdate(x)
+#endif
+
+#if !defined(HAVE_SSL_SESSION_GET_PROTOCOL_VERSION)
+#  define SSL_SESSION_get_protocol_version(s) ((s)->ssl_version)
+#endif
+
+#endif /* _OSSL_OPENSSL_MISSING_H_ */
diff --git a/ext/openssl/ossl.c b/ext/openssl/ossl.c
new file mode 100644
index 0000000..eb71b64
--- /dev/null
+++ b/ext/openssl/ossl.c
@@ -0,0 +1,1140 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2001-2002  Michal Rokos <m.rokos at sh.cvut.cz>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#include "ossl.h"
+#include <stdarg.h> /* for ossl_raise */
+#include <ruby/thread_native.h> /* for OpenSSL < 1.1.0 locks */
+
+/*
+ * Data Conversion
+ */
+#define OSSL_IMPL_ARY2SK(name, type, expected_class, dup)	\
+STACK_OF(type) *						\
+ossl_##name##_ary2sk0(VALUE ary)				\
+{								\
+    STACK_OF(type) *sk;						\
+    VALUE val;							\
+    type *x;							\
+    int i;							\
+    								\
+    Check_Type(ary, T_ARRAY);					\
+    sk = sk_##type##_new_null();				\
+    if (!sk) ossl_raise(eOSSLError, NULL);			\
+    								\
+    for (i = 0; i < RARRAY_LEN(ary); i++) {			\
+	val = rb_ary_entry(ary, i);				\
+	if (!rb_obj_is_kind_of(val, expected_class)) {		\
+	    sk_##type##_pop_free(sk, type##_free);		\
+	    ossl_raise(eOSSLError, "object in array not"	\
+		       " of class ##type##");			\
+	}							\
+	x = dup(val); /* NEED TO DUP */				\
+	sk_##type##_push(sk, x);				\
+    }								\
+    return sk;							\
+}								\
+								\
+STACK_OF(type) *						\
+ossl_protect_##name##_ary2sk(VALUE ary, int *status)		\
+{								\
+    return (STACK_OF(type)*)rb_protect(				\
+	    (VALUE (*)(VALUE))ossl_##name##_ary2sk0,		\
+	    ary,						\
+	    status);						\
+}								\
+								\
+STACK_OF(type) *						\
+ossl_##name##_ary2sk(VALUE ary)					\
+{								\
+    STACK_OF(type) *sk;						\
+    int status = 0;						\
+    								\
+    sk = ossl_protect_##name##_ary2sk(ary, &status);		\
+    if (status) rb_jump_tag(status);				\
+								\
+    return sk;							\
+}
+OSSL_IMPL_ARY2SK(x509, X509, cX509Cert, DupX509CertPtr)
+
+#define OSSL_IMPL_SK2ARY(name, type)	        \
+VALUE						\
+ossl_##name##_sk2ary(const STACK_OF(type) *sk)	\
+{						\
+    type *t;					\
+    int i, num;					\
+    VALUE ary;					\
+						\
+    if (!sk) {					\
+	OSSL_Debug("empty sk!");		\
+	return Qnil;				\
+    }						\
+    num = sk_##type##_num(sk);			\
+    if (num < 0) {				\
+	OSSL_Debug("items in sk < -1???");	\
+	return rb_ary_new();			\
+    }						\
+    ary = rb_ary_new2(num);			\
+						\
+    for (i=0; i<num; i++) {			\
+	t = sk_##type##_value(sk, i);		\
+	rb_ary_push(ary, ossl_##name##_new(t));	\
+    }						\
+    return ary;					\
+}
+OSSL_IMPL_SK2ARY(x509, X509)
+OSSL_IMPL_SK2ARY(x509crl, X509_CRL)
+OSSL_IMPL_SK2ARY(x509name, X509_NAME)
+
+static VALUE
+ossl_str_new(int size)
+{
+    return rb_str_new(0, size);
+}
+
+VALUE
+ossl_buf2str(char *buf, int len)
+{
+    VALUE str;
+    int status = 0;
+
+    str = rb_protect((VALUE (*)(VALUE))ossl_str_new, len, &status);
+    if(!NIL_P(str)) memcpy(RSTRING_PTR(str), buf, len);
+    OPENSSL_free(buf);
+    if(status) rb_jump_tag(status);
+
+    return str;
+}
+
+void
+ossl_bin2hex(unsigned char *in, char *out, size_t inlen)
+{
+    const char *hex = "0123456789abcdef";
+    size_t i;
+
+    assert(inlen <= LONG_MAX / 2);
+    for (i = 0; i < inlen; i++) {
+	unsigned char p = in[i];
+
+	out[i * 2 + 0] = hex[p >> 4];
+	out[i * 2 + 1] = hex[p & 0x0f];
+    }
+}
+
+/*
+ * our default PEM callback
+ */
+
+/*
+ * OpenSSL requires passwords for PEM-encoded files to be at least four
+ * characters long. See crypto/pem/pem_lib.c (as of 1.0.2h)
+ */
+#define OSSL_MIN_PWD_LEN 4
+
+VALUE
+ossl_pem_passwd_value(VALUE pass)
+{
+    if (NIL_P(pass))
+	return Qnil;
+
+    StringValue(pass);
+
+    if (RSTRING_LEN(pass) < OSSL_MIN_PWD_LEN)
+	ossl_raise(eOSSLError, "password must be at least %d bytes", OSSL_MIN_PWD_LEN);
+    /* PEM_BUFSIZE is currently used as the second argument of pem_password_cb,
+     * that is +max_len+ of ossl_pem_passwd_cb() */
+    if (RSTRING_LEN(pass) > PEM_BUFSIZE)
+	ossl_raise(eOSSLError, "password must not be longer than %d bytes", PEM_BUFSIZE);
+
+    return pass;
+}
+
+static VALUE
+ossl_pem_passwd_cb0(VALUE flag)
+{
+    VALUE pass;
+
+    pass = rb_yield(flag);
+    SafeStringValue(pass);
+
+    return pass;
+}
+
+int
+ossl_pem_passwd_cb(char *buf, int max_len, int flag, void *pwd_)
+{
+    long len;
+    int status;
+    VALUE rflag, pass = (VALUE)pwd_;
+
+    if (RTEST(pass)) {
+	/* PEM_def_callback(buf, max_len, flag, StringValueCStr(pass)) does not
+	 * work because it does not allow NUL characters and truncates to 1024
+	 * bytes silently if the input is over 1024 bytes */
+	if (RB_TYPE_P(pass, T_STRING)) {
+	    len = RSTRING_LEN(pass);
+	    if (len >= OSSL_MIN_PWD_LEN && len <= max_len) {
+		memcpy(buf, RSTRING_PTR(pass), len);
+		return (int)len;
+	    }
+	}
+	OSSL_Debug("passed data is not valid String???");
+	return -1;
+    }
+
+    if (!rb_block_given_p()) {
+	return PEM_def_callback(buf, max_len, flag, NULL);
+    }
+
+    while (1) {
+	/*
+	 * when the flag is nonzero, this passphrase
+	 * will be used to perform encryption; otherwise it will
+	 * be used to perform decryption.
+	 */
+	rflag = flag ? Qtrue : Qfalse;
+	pass  = rb_protect(ossl_pem_passwd_cb0, rflag, &status);
+	if (status) {
+	    /* ignore an exception raised. */
+	    rb_set_errinfo(Qnil);
+	    return -1;
+	}
+	len = RSTRING_LEN(pass);
+	if (len < OSSL_MIN_PWD_LEN) {
+	    rb_warning("password must be at least %d bytes", OSSL_MIN_PWD_LEN);
+	    continue;
+	}
+	if (len > max_len) {
+	    rb_warning("password must not be longer than %d bytes", max_len);
+	    continue;
+	}
+	memcpy(buf, RSTRING_PTR(pass), len);
+	break;
+    }
+    return (int)len;
+}
+
+/*
+ * main module
+ */
+VALUE mOSSL;
+
+/*
+ * OpenSSLError < StandardError
+ */
+VALUE eOSSLError;
+
+/*
+ * Convert to DER string
+ */
+ID ossl_s_to_der;
+
+VALUE
+ossl_to_der(VALUE obj)
+{
+    VALUE tmp;
+
+    tmp = rb_funcall(obj, ossl_s_to_der, 0);
+    StringValue(tmp);
+
+    return tmp;
+}
+
+VALUE
+ossl_to_der_if_possible(VALUE obj)
+{
+    if(rb_respond_to(obj, ossl_s_to_der))
+	return ossl_to_der(obj);
+    return obj;
+}
+
+/*
+ * Errors
+ */
+static VALUE
+ossl_make_error(VALUE exc, const char *fmt, va_list args)
+{
+    VALUE str = Qnil;
+    const char *msg;
+    long e;
+
+    e = ERR_peek_last_error();
+    if (fmt) {
+	str = rb_vsprintf(fmt, args);
+    }
+    if (e) {
+	if (dOSSL == Qtrue) /* FULL INFO */
+	    msg = ERR_error_string(e, NULL);
+	else
+	    msg = ERR_reason_error_string(e);
+	if (NIL_P(str)) {
+	    if (msg) str = rb_str_new_cstr(msg);
+	}
+	else {
+	    if (RSTRING_LEN(str)) rb_str_cat2(str, ": ");
+	    rb_str_cat2(str, msg ? msg : "(null)");
+	}
+    }
+    ossl_clear_error();
+
+    if (NIL_P(str)) str = rb_str_new(0, 0);
+    return rb_exc_new3(exc, str);
+}
+
+void
+ossl_raise(VALUE exc, const char *fmt, ...)
+{
+    va_list args;
+    VALUE err;
+    va_start(args, fmt);
+    err = ossl_make_error(exc, fmt, args);
+    va_end(args);
+    rb_exc_raise(err);
+}
+
+void
+ossl_clear_error(void)
+{
+    if (dOSSL == Qtrue) {
+	unsigned long e;
+	const char *file, *data, *errstr;
+	int line, flags;
+
+	while ((e = ERR_get_error_line_data(&file, &line, &data, &flags))) {
+	    errstr = ERR_error_string(e, NULL);
+	    if (!errstr)
+		errstr = "(null)";
+
+	    if (flags & ERR_TXT_STRING) {
+		if (!data)
+		    data = "(null)";
+		rb_warn("error on stack: %s (%s)", errstr, data);
+	    }
+	    else {
+		rb_warn("error on stack: %s", errstr);
+	    }
+	}
+    }
+    else {
+	ERR_clear_error();
+    }
+}
+
+/*
+ * call-seq:
+ *   OpenSSL.errors -> [String...]
+ *
+ * See any remaining errors held in queue.
+ *
+ * Any errors you see here are probably due to a bug in ruby's OpenSSL implementation.
+ */
+VALUE
+ossl_get_errors(void)
+{
+    VALUE ary;
+    long e;
+
+    ary = rb_ary_new();
+    while ((e = ERR_get_error()) != 0){
+        rb_ary_push(ary, rb_str_new2(ERR_error_string(e, NULL)));
+    }
+
+    return ary;
+}
+
+/*
+ * Debug
+ */
+VALUE dOSSL;
+
+#if !defined(HAVE_VA_ARGS_MACRO)
+void
+ossl_debug(const char *fmt, ...)
+{
+    va_list args;
+
+    if (dOSSL == Qtrue) {
+	fprintf(stderr, "OSSL_DEBUG: ");
+	va_start(args, fmt);
+	vfprintf(stderr, fmt, args);
+	va_end(args);
+	fprintf(stderr, " [CONTEXT N/A]\n");
+    }
+}
+#endif
+
+/*
+ * call-seq:
+ *   OpenSSL.debug -> true | false
+ */
+static VALUE
+ossl_debug_get(VALUE self)
+{
+    return dOSSL;
+}
+
+/*
+ * call-seq:
+ *   OpenSSL.debug = boolean -> boolean
+ *
+ * Turns on or off debug mode. With debug mode, all erros added to the OpenSSL
+ * error queue will be printed to stderr.
+ */
+static VALUE
+ossl_debug_set(VALUE self, VALUE val)
+{
+    dOSSL = RTEST(val) ? Qtrue : Qfalse;
+
+    return val;
+}
+
+/*
+ * call-seq:
+ *   OpenSSL.fips_mode = boolean -> boolean
+ *
+ * Turns FIPS mode on or off. Turning on FIPS mode will obviously only have an
+ * effect for FIPS-capable installations of the OpenSSL library. Trying to do
+ * so otherwise will result in an error.
+ *
+ * === Examples
+ *   OpenSSL.fips_mode = true   # turn FIPS mode on
+ *   OpenSSL.fips_mode = false  # and off again
+ */
+static VALUE
+ossl_fips_mode_set(VALUE self, VALUE enabled)
+{
+
+#ifdef OPENSSL_FIPS
+    if (RTEST(enabled)) {
+	int mode = FIPS_mode();
+	if(!mode && !FIPS_mode_set(1)) /* turning on twice leads to an error */
+	    ossl_raise(eOSSLError, "Turning on FIPS mode failed");
+    } else {
+	if(!FIPS_mode_set(0)) /* turning off twice is OK */
+	    ossl_raise(eOSSLError, "Turning off FIPS mode failed");
+    }
+    return enabled;
+#else
+    if (RTEST(enabled))
+	ossl_raise(eOSSLError, "This version of OpenSSL does not support FIPS mode");
+    return enabled;
+#endif
+}
+
+#if !defined(HAVE_OPENSSL_110_THREADING_API)
+/**
+ * Stores locks needed for OpenSSL thread safety
+ */
+static rb_nativethread_lock_t *ossl_locks;
+
+static void
+ossl_lock_unlock(int mode, rb_nativethread_lock_t *lock)
+{
+    if (mode & CRYPTO_LOCK) {
+	rb_nativethread_lock_lock(lock);
+    } else {
+	rb_nativethread_lock_unlock(lock);
+    }
+}
+
+static void
+ossl_lock_callback(int mode, int type, const char *file, int line)
+{
+    ossl_lock_unlock(mode, &ossl_locks[type]);
+}
+
+struct CRYPTO_dynlock_value {
+    rb_nativethread_lock_t lock;
+};
+
+static struct CRYPTO_dynlock_value *
+ossl_dyn_create_callback(const char *file, int line)
+{
+    struct CRYPTO_dynlock_value *dynlock = (struct CRYPTO_dynlock_value *)OPENSSL_malloc((int)sizeof(struct CRYPTO_dynlock_value));
+    rb_nativethread_lock_initialize(&dynlock->lock);
+    return dynlock;
+}
+
+static void
+ossl_dyn_lock_callback(int mode, struct CRYPTO_dynlock_value *l, const char *file, int line)
+{
+    ossl_lock_unlock(mode, &l->lock);
+}
+
+static void
+ossl_dyn_destroy_callback(struct CRYPTO_dynlock_value *l, const char *file, int line)
+{
+    rb_nativethread_lock_destroy(&l->lock);
+    OPENSSL_free(l);
+}
+
+#ifdef HAVE_CRYPTO_THREADID_PTR
+static void ossl_threadid_func(CRYPTO_THREADID *id)
+{
+    /* register native thread id */
+    CRYPTO_THREADID_set_pointer(id, (void *)rb_nativethread_self());
+}
+#else
+static unsigned long ossl_thread_id(void)
+{
+    /* before OpenSSL 1.0, this is 'unsigned long' */
+    return (unsigned long)rb_nativethread_self();
+}
+#endif
+
+static void Init_ossl_locks(void)
+{
+    int i;
+    int num_locks = CRYPTO_num_locks();
+
+    if ((unsigned)num_locks >= INT_MAX / (int)sizeof(VALUE)) {
+	rb_raise(rb_eRuntimeError, "CRYPTO_num_locks() is too big: %d", num_locks);
+    }
+    ossl_locks = (rb_nativethread_lock_t *) OPENSSL_malloc(num_locks * (int)sizeof(rb_nativethread_lock_t));
+    if (!ossl_locks) {
+	rb_raise(rb_eNoMemError, "CRYPTO_num_locks() is too big: %d", num_locks);
+    }
+    for (i = 0; i < num_locks; i++) {
+	rb_nativethread_lock_initialize(&ossl_locks[i]);
+    }
+
+#ifdef HAVE_CRYPTO_THREADID_PTR
+    CRYPTO_THREADID_set_callback(ossl_threadid_func);
+#else
+    CRYPTO_set_id_callback(ossl_thread_id);
+#endif
+    CRYPTO_set_locking_callback(ossl_lock_callback);
+    CRYPTO_set_dynlock_create_callback(ossl_dyn_create_callback);
+    CRYPTO_set_dynlock_lock_callback(ossl_dyn_lock_callback);
+    CRYPTO_set_dynlock_destroy_callback(ossl_dyn_destroy_callback);
+}
+#endif /* !HAVE_OPENSSL_110_THREADING_API */
+
+/*
+ * OpenSSL provides SSL, TLS and general purpose cryptography.  It wraps the
+ * OpenSSL[http://www.openssl.org/] library.
+ *
+ * = Examples
+ *
+ * All examples assume you have loaded OpenSSL with:
+ *
+ *   require 'openssl'
+ *
+ * These examples build atop each other.  For example the key created in the
+ * next is used in throughout these examples.
+ *
+ * == Keys
+ *
+ * === Creating a Key
+ *
+ * This example creates a 2048 bit RSA keypair and writes it to the current
+ * directory.
+ *
+ *   key = OpenSSL::PKey::RSA.new 2048
+ *
+ *   open 'private_key.pem', 'w' do |io| io.write key.to_pem end
+ *   open 'public_key.pem', 'w' do |io| io.write key.public_key.to_pem end
+ *
+ * === Exporting a Key
+ *
+ * Keys saved to disk without encryption are not secure as anyone who gets
+ * ahold of the key may use it unless it is encrypted.  In order to securely
+ * export a key you may export it with a pass phrase.
+ *
+ *   cipher = OpenSSL::Cipher.new 'AES-128-CBC'
+ *   pass_phrase = 'my secure pass phrase goes here'
+ *
+ *   key_secure = key.export cipher, pass_phrase
+ *
+ *   open 'private.secure.pem', 'w' do |io|
+ *     io.write key_secure
+ *   end
+ *
+ * OpenSSL::Cipher.ciphers returns a list of available ciphers.
+ *
+ * === Loading a Key
+ *
+ * A key can also be loaded from a file.
+ *
+ *   key2 = OpenSSL::PKey::RSA.new File.read 'private_key.pem'
+ *   key2.public? # => true
+ *   key2.private? # => true
+ *
+ * or
+ *
+ *   key3 = OpenSSL::PKey::RSA.new File.read 'public_key.pem'
+ *   key3.public? # => true
+ *   key3.private? # => false
+ *
+ * === Loading an Encrypted Key
+ *
+ * OpenSSL will prompt you for your pass phrase when loading an encrypted key.
+ * If you will not be able to type in the pass phrase you may provide it when
+ * loading the key:
+ *
+ *   key4_pem = File.read 'private.secure.pem'
+ *   pass_phrase = 'my secure pass phrase goes here'
+ *   key4 = OpenSSL::PKey::RSA.new key4_pem, pass_phrase
+ *
+ * == RSA Encryption
+ *
+ * RSA provides encryption and decryption using the public and private keys.
+ * You can use a variety of padding methods depending upon the intended use of
+ * encrypted data.
+ *
+ * === Encryption & Decryption
+ *
+ * Asymmetric public/private key encryption is slow and victim to attack in
+ * cases where it is used without padding or directly to encrypt larger chunks
+ * of data. Typical use cases for RSA encryption involve "wrapping" a symmetric
+ * key with the public key of the recipient who would "unwrap" that symmetric
+ * key again using their private key.
+ * The following illustrates a simplified example of such a key transport
+ * scheme. It shouldn't be used in practice, though, standardized protocols
+ * should always be preferred.
+ *
+ *   wrapped_key = key.public_encrypt key
+ *
+ * A symmetric key encrypted with the public key can only be decrypted with
+ * the corresponding private key of the recipient.
+ *
+ *   original_key = key.private_decrypt wrapped_key
+ *
+ * By default PKCS#1 padding will be used, but it is also possible to use
+ * other forms of padding, see PKey::RSA for further details.
+ *
+ * === Signatures
+ *
+ * Using "private_encrypt" to encrypt some data with the private key is
+ * equivalent to applying a digital signature to the data. A verifying
+ * party may validate the signature by comparing the result of decrypting
+ * the signature with "public_decrypt" to the original data. However,
+ * OpenSSL::PKey already has methods "sign" and "verify" that handle
+ * digital signatures in a standardized way - "private_encrypt" and
+ * "public_decrypt" shouldn't be used in practice.
+ *
+ * To sign a document, a cryptographically secure hash of the document is
+ * computed first, which is then signed using the private key.
+ *
+ *   digest = OpenSSL::Digest::SHA256.new
+ *   signature = key.sign digest, document
+ *
+ * To validate the signature, again a hash of the document is computed and
+ * the signature is decrypted using the public key. The result is then
+ * compared to the hash just computed, if they are equal the signature was
+ * valid.
+ *
+ *   digest = OpenSSL::Digest::SHA256.new
+ *   if key.verify digest, signature, document
+ *     puts 'Valid'
+ *   else
+ *     puts 'Invalid'
+ *   end
+ *
+ * == PBKDF2 Password-based Encryption
+ *
+ * If supported by the underlying OpenSSL version used, Password-based
+ * Encryption should use the features of PKCS5. If not supported or if
+ * required by legacy applications, the older, less secure methods specified
+ * in RFC 2898 are also supported (see below).
+ *
+ * PKCS5 supports PBKDF2 as it was specified in PKCS#5
+ * v2.0[http://www.rsa.com/rsalabs/node.asp?id=2127]. It still uses a
+ * password, a salt, and additionally a number of iterations that will
+ * slow the key derivation process down. The slower this is, the more work
+ * it requires being able to brute-force the resulting key.
+ *
+ * === Encryption
+ *
+ * The strategy is to first instantiate a Cipher for encryption, and
+ * then to generate a random IV plus a key derived from the password
+ * using PBKDF2. PKCS #5 v2.0 recommends at least 8 bytes for the salt,
+ * the number of iterations largely depends on the hardware being used.
+ *
+ *   cipher = OpenSSL::Cipher.new 'AES-128-CBC'
+ *   cipher.encrypt
+ *   iv = cipher.random_iv
+ *
+ *   pwd = 'some hopefully not to easily guessable password'
+ *   salt = OpenSSL::Random.random_bytes 16
+ *   iter = 20000
+ *   key_len = cipher.key_len
+ *   digest = OpenSSL::Digest::SHA256.new
+ *
+ *   key = OpenSSL::PKCS5.pbkdf2_hmac(pwd, salt, iter, key_len, digest)
+ *   cipher.key = key
+ *
+ *   Now encrypt the data:
+ *
+ *   encrypted = cipher.update document
+ *   encrypted << cipher.final
+ *
+ * === Decryption
+ *
+ * Use the same steps as before to derive the symmetric AES key, this time
+ * setting the Cipher up for decryption.
+ *
+ *   cipher = OpenSSL::Cipher.new 'AES-128-CBC'
+ *   cipher.decrypt
+ *   cipher.iv = iv # the one generated with #random_iv
+ *
+ *   pwd = 'some hopefully not to easily guessable password'
+ *   salt = ... # the one generated above
+ *   iter = 20000
+ *   key_len = cipher.key_len
+ *   digest = OpenSSL::Digest::SHA256.new
+ *
+ *   key = OpenSSL::PKCS5.pbkdf2_hmac(pwd, salt, iter, key_len, digest)
+ *   cipher.key = key
+ *
+ *   Now decrypt the data:
+ *
+ *   decrypted = cipher.update encrypted
+ *   decrypted << cipher.final
+ *
+ * == PKCS #5 Password-based Encryption
+ *
+ * PKCS #5 is a password-based encryption standard documented at
+ * RFC2898[http://www.ietf.org/rfc/rfc2898.txt].  It allows a short password or
+ * passphrase to be used to create a secure encryption key. If possible, PBKDF2
+ * as described above should be used if the circumstances allow it.
+ *
+ * PKCS #5 uses a Cipher, a pass phrase and a salt to generate an encryption
+ * key.
+ *
+ *   pass_phrase = 'my secure pass phrase goes here'
+ *   salt = '8 octets'
+ *
+ * === Encryption
+ *
+ * First set up the cipher for encryption
+ *
+ *   encryptor = OpenSSL::Cipher.new 'AES-128-CBC'
+ *   encryptor.encrypt
+ *   encryptor.pkcs5_keyivgen pass_phrase, salt
+ *
+ * Then pass the data you want to encrypt through
+ *
+ *   encrypted = encryptor.update 'top secret document'
+ *   encrypted << encryptor.final
+ *
+ * === Decryption
+ *
+ * Use a new Cipher instance set up for decryption
+ *
+ *   decryptor = OpenSSL::Cipher.new 'AES-128-CBC'
+ *   decryptor.decrypt
+ *   decryptor.pkcs5_keyivgen pass_phrase, salt
+ *
+ * Then pass the data you want to decrypt through
+ *
+ *   plain = decryptor.update encrypted
+ *   plain << decryptor.final
+ *
+ * == X509 Certificates
+ *
+ * === Creating a Certificate
+ *
+ * This example creates a self-signed certificate using an RSA key and a SHA1
+ * signature.
+ *
+ *   key = OpenSSL::PKey::RSA.new 2048
+ *   name = OpenSSL::X509::Name.parse 'CN=nobody/DC=example'
+ *
+ *   cert = OpenSSL::X509::Certificate.new
+ *   cert.version = 2
+ *   cert.serial = 0
+ *   cert.not_before = Time.now
+ *   cert.not_after = Time.now + 3600
+ *
+ *   cert.public_key = key.public_key
+ *   cert.subject = name
+ *
+ * === Certificate Extensions
+ *
+ * You can add extensions to the certificate with
+ * OpenSSL::SSL::ExtensionFactory to indicate the purpose of the certificate.
+ *
+ *   extension_factory = OpenSSL::X509::ExtensionFactory.new nil, cert
+ *
+ *   cert.add_extension \
+ *     extension_factory.create_extension('basicConstraints', 'CA:FALSE', true)
+ *
+ *   cert.add_extension \
+ *     extension_factory.create_extension(
+ *       'keyUsage', 'keyEncipherment,dataEncipherment,digitalSignature')
+ *
+ *   cert.add_extension \
+ *     extension_factory.create_extension('subjectKeyIdentifier', 'hash')
+ *
+ * The list of supported extensions (and in some cases their possible values)
+ * can be derived from the "objects.h" file in the OpenSSL source code.
+ *
+ * === Signing a Certificate
+ *
+ * To sign a certificate set the issuer and use OpenSSL::X509::Certificate#sign
+ * with a digest algorithm.  This creates a self-signed cert because we're using
+ * the same name and key to sign the certificate as was used to create the
+ * certificate.
+ *
+ *   cert.issuer = name
+ *   cert.sign key, OpenSSL::Digest::SHA1.new
+ *
+ *   open 'certificate.pem', 'w' do |io| io.write cert.to_pem end
+ *
+ * === Loading a Certificate
+ *
+ * Like a key, a cert can also be loaded from a file.
+ *
+ *   cert2 = OpenSSL::X509::Certificate.new File.read 'certificate.pem'
+ *
+ * === Verifying a Certificate
+ *
+ * Certificate#verify will return true when a certificate was signed with the
+ * given public key.
+ *
+ *   raise 'certificate can not be verified' unless cert2.verify key
+ *
+ * == Certificate Authority
+ *
+ * A certificate authority (CA) is a trusted third party that allows you to
+ * verify the ownership of unknown certificates.  The CA issues key signatures
+ * that indicate it trusts the user of that key.  A user encountering the key
+ * can verify the signature by using the CA's public key.
+ *
+ * === CA Key
+ *
+ * CA keys are valuable, so we encrypt and save it to disk and make sure it is
+ * not readable by other users.
+ *
+ *   ca_key = OpenSSL::PKey::RSA.new 2048
+ *   pass_phrase = 'my secure pass phrase goes here'
+ *
+ *   cipher = OpenSSL::Cipher.new 'AES-128-CBC'
+ *
+ *   open 'ca_key.pem', 'w', 0400 do |io|
+ *     io.write ca_key.export(cipher, pass_phrase)
+ *   end
+ *
+ * === CA Certificate
+ *
+ * A CA certificate is created the same way we created a certificate above, but
+ * with different extensions.
+ *
+ *   ca_name = OpenSSL::X509::Name.parse 'CN=ca/DC=example'
+ *
+ *   ca_cert = OpenSSL::X509::Certificate.new
+ *   ca_cert.serial = 0
+ *   ca_cert.version = 2
+ *   ca_cert.not_before = Time.now
+ *   ca_cert.not_after = Time.now + 86400
+ *
+ *   ca_cert.public_key = ca_key.public_key
+ *   ca_cert.subject = ca_name
+ *   ca_cert.issuer = ca_name
+ *
+ *   extension_factory = OpenSSL::X509::ExtensionFactory.new
+ *   extension_factory.subject_certificate = ca_cert
+ *   extension_factory.issuer_certificate = ca_cert
+ *
+ *   ca_cert.add_extension \
+ *     extension_factory.create_extension('subjectKeyIdentifier', 'hash')
+ *
+ * This extension indicates the CA's key may be used as a CA.
+ *
+ *   ca_cert.add_extension \
+ *     extension_factory.create_extension('basicConstraints', 'CA:TRUE', true)
+ *
+ * This extension indicates the CA's key may be used to verify signatures on
+ * both certificates and certificate revocations.
+ *
+ *   ca_cert.add_extension \
+ *     extension_factory.create_extension(
+ *       'keyUsage', 'cRLSign,keyCertSign', true)
+ *
+ * Root CA certificates are self-signed.
+ *
+ *   ca_cert.sign ca_key, OpenSSL::Digest::SHA1.new
+ *
+ * The CA certificate is saved to disk so it may be distributed to all the
+ * users of the keys this CA will sign.
+ *
+ *   open 'ca_cert.pem', 'w' do |io|
+ *     io.write ca_cert.to_pem
+ *   end
+ *
+ * === Certificate Signing Request
+ *
+ * The CA signs keys through a Certificate Signing Request (CSR).  The CSR
+ * contains the information necessary to identify the key.
+ *
+ *   csr = OpenSSL::X509::Request.new
+ *   csr.version = 0
+ *   csr.subject = name
+ *   csr.public_key = key.public_key
+ *   csr.sign key, OpenSSL::Digest::SHA1.new
+ *
+ * A CSR is saved to disk and sent to the CA for signing.
+ *
+ *   open 'csr.pem', 'w' do |io|
+ *     io.write csr.to_pem
+ *   end
+ *
+ * === Creating a Certificate from a CSR
+ *
+ * Upon receiving a CSR the CA will verify it before signing it.  A minimal
+ * verification would be to check the CSR's signature.
+ *
+ *   csr = OpenSSL::X509::Request.new File.read 'csr.pem'
+ *
+ *   raise 'CSR can not be verified' unless csr.verify csr.public_key
+ *
+ * After verification a certificate is created, marked for various usages,
+ * signed with the CA key and returned to the requester.
+ *
+ *   csr_cert = OpenSSL::X509::Certificate.new
+ *   csr_cert.serial = 0
+ *   csr_cert.version = 2
+ *   csr_cert.not_before = Time.now
+ *   csr_cert.not_after = Time.now + 600
+ *
+ *   csr_cert.subject = csr.subject
+ *   csr_cert.public_key = csr.public_key
+ *   csr_cert.issuer = ca_cert.subject
+ *
+ *   extension_factory = OpenSSL::X509::ExtensionFactory.new
+ *   extension_factory.subject_certificate = csr_cert
+ *   extension_factory.issuer_certificate = ca_cert
+ *
+ *   csr_cert.add_extension \
+ *     extension_factory.create_extension('basicConstraints', 'CA:FALSE')
+ *
+ *   csr_cert.add_extension \
+ *     extension_factory.create_extension(
+ *       'keyUsage', 'keyEncipherment,dataEncipherment,digitalSignature')
+ *
+ *   csr_cert.add_extension \
+ *     extension_factory.create_extension('subjectKeyIdentifier', 'hash')
+ *
+ *   csr_cert.sign ca_key, OpenSSL::Digest::SHA1.new
+ *
+ *   open 'csr_cert.pem', 'w' do |io|
+ *     io.write csr_cert.to_pem
+ *   end
+ *
+ * == SSL and TLS Connections
+ *
+ * Using our created key and certificate we can create an SSL or TLS connection.
+ * An SSLContext is used to set up an SSL session.
+ *
+ *   context = OpenSSL::SSL::SSLContext.new
+ *
+ * === SSL Server
+ *
+ * An SSL server requires the certificate and private key to communicate
+ * securely with its clients:
+ *
+ *   context.cert = cert
+ *   context.key = key
+ *
+ * Then create an SSLServer with a TCP server socket and the context.  Use the
+ * SSLServer like an ordinary TCP server.
+ *
+ *   require 'socket'
+ *
+ *   tcp_server = TCPServer.new 5000
+ *   ssl_server = OpenSSL::SSL::SSLServer.new tcp_server, context
+ *
+ *   loop do
+ *     ssl_connection = ssl_server.accept
+ *
+ *     data = connection.gets
+ *
+ *     response = "I got #{data.dump}"
+ *     puts response
+ *
+ *     connection.puts "I got #{data.dump}"
+ *     connection.close
+ *   end
+ *
+ * === SSL client
+ *
+ * An SSL client is created with a TCP socket and the context.
+ * SSLSocket#connect must be called to initiate the SSL handshake and start
+ * encryption.  A key and certificate are not required for the client socket.
+ *
+ * Note that SSLSocket#close doesn't close the underlying socket by default. Set
+ * SSLSocket#sync_close to true if you want.
+ *
+ *   require 'socket'
+ *
+ *   tcp_socket = TCPSocket.new 'localhost', 5000
+ *   ssl_client = OpenSSL::SSL::SSLSocket.new tcp_socket, context
+ *   ssl_client.sync_close = true
+ *   ssl_client.connect
+ *
+ *   ssl_client.puts "hello server!"
+ *   puts ssl_client.gets
+ *
+ *   ssl_client.close # shutdown the TLS connection and close tcp_socket
+ *
+ * === Peer Verification
+ *
+ * An unverified SSL connection does not provide much security.  For enhanced
+ * security the client or server can verify the certificate of its peer.
+ *
+ * The client can be modified to verify the server's certificate against the
+ * certificate authority's certificate:
+ *
+ *   context.ca_file = 'ca_cert.pem'
+ *   context.verify_mode = OpenSSL::SSL::VERIFY_PEER
+ *
+ *   require 'socket'
+ *
+ *   tcp_socket = TCPSocket.new 'localhost', 5000
+ *   ssl_client = OpenSSL::SSL::SSLSocket.new tcp_socket, context
+ *   ssl_client.connect
+ *
+ *   ssl_client.puts "hello server!"
+ *   puts ssl_client.gets
+ *
+ * If the server certificate is invalid or <tt>context.ca_file</tt> is not set
+ * when verifying peers an OpenSSL::SSL::SSLError will be raised.
+ *
+ */
+void
+Init_openssl(void)
+{
+    /*
+     * Init timezone info
+     */
+#if 0
+    tzset();
+#endif
+
+    /*
+     * Init all digests, ciphers
+     */
+    /* CRYPTO_malloc_init(); */
+    /* ENGINE_load_builtin_engines(); */
+    OpenSSL_add_ssl_algorithms();
+    OpenSSL_add_all_algorithms();
+    ERR_load_crypto_strings();
+    SSL_load_error_strings();
+
+    /*
+     * FIXME:
+     * On unload do:
+     */
+#if 0
+    CONF_modules_unload(1);
+    destroy_ui_method();
+    EVP_cleanup();
+    ENGINE_cleanup();
+    CRYPTO_cleanup_all_ex_data();
+    ERR_remove_state(0);
+    ERR_free_strings();
+#endif
+
+    /*
+     * Init main module
+     */
+    mOSSL = rb_define_module("OpenSSL");
+    rb_global_variable(&mOSSL);
+
+    /*
+     * OpenSSL ruby extension version
+     */
+    rb_define_const(mOSSL, "VERSION", rb_str_new2(OSSL_VERSION));
+
+    /*
+     * Version of OpenSSL the ruby OpenSSL extension was built with
+     */
+    rb_define_const(mOSSL, "OPENSSL_VERSION", rb_str_new2(OPENSSL_VERSION_TEXT));
+
+    /*
+     * Version of OpenSSL the ruby OpenSSL extension is running with
+     */
+    rb_define_const(mOSSL, "OPENSSL_LIBRARY_VERSION", rb_str_new2(SSLeay_version(SSLEAY_VERSION)));
+
+    /*
+     * Version number of OpenSSL the ruby OpenSSL extension was built with
+     * (base 16)
+     */
+    rb_define_const(mOSSL, "OPENSSL_VERSION_NUMBER", INT2NUM(OPENSSL_VERSION_NUMBER));
+
+    /*
+     * Boolean indicating whether OpenSSL is FIPS-enabled or not
+     */
+    rb_define_const(mOSSL, "OPENSSL_FIPS",
+#ifdef OPENSSL_FIPS
+		    Qtrue
+#else
+		    Qfalse
+#endif
+		   );
+
+    rb_define_module_function(mOSSL, "fips_mode=", ossl_fips_mode_set, 1);
+
+    /*
+     * Generic error,
+     * common for all classes under OpenSSL module
+     */
+    eOSSLError = rb_define_class_under(mOSSL,"OpenSSLError",rb_eStandardError);
+    rb_global_variable(&eOSSLError);
+
+    /*
+     * Init debug core
+     */
+    dOSSL = Qfalse;
+    rb_global_variable(&dOSSL);
+
+    rb_define_module_function(mOSSL, "debug", ossl_debug_get, 0);
+    rb_define_module_function(mOSSL, "debug=", ossl_debug_set, 1);
+    rb_define_module_function(mOSSL, "errors", ossl_get_errors, 0);
+
+    /*
+     * Get ID of to_der
+     */
+    ossl_s_to_der = rb_intern("to_der");
+
+#if !defined(HAVE_OPENSSL_110_THREADING_API)
+    Init_ossl_locks();
+#endif
+
+    /*
+     * Init components
+     */
+    Init_ossl_bn();
+    Init_ossl_cipher();
+    Init_ossl_config();
+    Init_ossl_digest();
+    Init_ossl_hmac();
+    Init_ossl_ns_spki();
+    Init_ossl_pkcs12();
+    Init_ossl_pkcs7();
+    Init_ossl_pkcs5();
+    Init_ossl_pkey();
+    Init_ossl_rand();
+    Init_ossl_ssl();
+    Init_ossl_x509();
+    Init_ossl_ocsp();
+    Init_ossl_engine();
+    Init_ossl_asn1();
+}
+
+#if defined(OSSL_DEBUG)
+/*
+ * Check if all symbols are OK with 'make LDSHARED=gcc all'
+ */
+int
+main(int argc, char *argv[])
+{
+    return 0;
+}
+#endif /* OSSL_DEBUG */
diff --git a/ext/openssl/ossl.h b/ext/openssl/ossl.h
new file mode 100644
index 0000000..78eddd0
--- /dev/null
+++ b/ext/openssl/ossl.h
@@ -0,0 +1,186 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2001-2002  Michal Rokos <m.rokos at sh.cvut.cz>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#if !defined(_OSSL_H_)
+#define _OSSL_H_
+
+#include RUBY_EXTCONF_H
+
+#include <assert.h>
+#include <errno.h>
+#include <ruby.h>
+#include <ruby/io.h>
+#include <ruby/thread.h>
+#include <openssl/opensslv.h>
+#include <openssl/err.h>
+#include <openssl/asn1.h>
+#include <openssl/x509v3.h>
+#include <openssl/ssl.h>
+#include <openssl/pkcs12.h>
+#include <openssl/pkcs7.h>
+#include <openssl/hmac.h>
+#include <openssl/rand.h>
+#include <openssl/conf.h>
+#include <openssl/conf_api.h>
+#include <openssl/crypto.h>
+#if !defined(OPENSSL_NO_ENGINE)
+#  include <openssl/engine.h>
+#endif
+#if !defined(OPENSSL_NO_OCSP)
+#  include <openssl/ocsp.h>
+#endif
+
+/*
+ * Common Module
+ */
+extern VALUE mOSSL;
+
+/*
+ * Common Error Class
+ */
+extern VALUE eOSSLError;
+
+/*
+ * CheckTypes
+ */
+#define OSSL_Check_Kind(obj, klass) do {\
+  if (!rb_obj_is_kind_of((obj), (klass))) {\
+    ossl_raise(rb_eTypeError, "wrong argument (%"PRIsVALUE")! (Expected kind of %"PRIsVALUE")",\
+               rb_obj_class(obj), (klass));\
+  }\
+} while (0)
+
+#define OSSL_Check_Instance(obj, klass) do {\
+  if (!rb_obj_is_instance_of((obj), (klass))) {\
+    ossl_raise(rb_eTypeError, "wrong argument (%"PRIsVALUE")! (Expected instance of %"PRIsVALUE")",\
+               rb_obj_class(obj), (klass));\
+  }\
+} while (0)
+
+#define OSSL_Check_Same_Class(obj1, obj2) do {\
+  if (!rb_obj_is_instance_of((obj1), rb_obj_class(obj2))) {\
+    ossl_raise(rb_eTypeError, "wrong argument type");\
+  }\
+} while (0)
+
+/*
+ * Data Conversion
+ */
+STACK_OF(X509) *ossl_x509_ary2sk0(VALUE);
+STACK_OF(X509) *ossl_x509_ary2sk(VALUE);
+STACK_OF(X509) *ossl_protect_x509_ary2sk(VALUE,int*);
+VALUE ossl_x509_sk2ary(const STACK_OF(X509) *certs);
+VALUE ossl_x509crl_sk2ary(const STACK_OF(X509_CRL) *crl);
+VALUE ossl_x509name_sk2ary(const STACK_OF(X509_NAME) *names);
+VALUE ossl_buf2str(char *buf, int len);
+#define ossl_str_adjust(str, p) \
+do{\
+    long len = RSTRING_LEN(str);\
+    long newlen = (long)((p) - (unsigned char*)RSTRING_PTR(str));\
+    assert(newlen <= len);\
+    rb_str_set_len((str), newlen);\
+}while(0)
+/*
+ * Convert binary string to hex string. The caller is responsible for
+ * ensuring out has (2 * len) bytes of capacity.
+ */
+void ossl_bin2hex(unsigned char *in, char *out, size_t len);
+
+/*
+ * Our default PEM callback
+ */
+/* Convert the argument to String and validate the length. Note this may raise. */
+VALUE ossl_pem_passwd_value(VALUE);
+/* Can be casted to pem_password_cb. If a password (String) is passed as the
+ * "arbitrary data" (typically the last parameter of PEM_{read,write}_
+ * functions), uses the value. If not, but a block is given, yields to it.
+ * If not either, fallbacks to PEM_def_callback() which reads from stdin. */
+int ossl_pem_passwd_cb(char *, int, int, void *);
+
+/*
+ * Clear BIO* with this in PEM/DER fallback scenarios to avoid decoding
+ * errors piling up in OpenSSL::Errors
+ */
+#define OSSL_BIO_reset(bio) do { \
+    (void)BIO_reset((bio)); \
+    ossl_clear_error(); \
+} while (0)
+
+/*
+ * ERRor messages
+ */
+#define OSSL_ErrMsg() ERR_reason_error_string(ERR_get_error())
+NORETURN(void ossl_raise(VALUE, const char *, ...));
+/* Clear OpenSSL error queue. If dOSSL is set, rb_warn() them. */
+void ossl_clear_error(void);
+
+/*
+ * String to DER String
+ */
+extern ID ossl_s_to_der;
+VALUE ossl_to_der(VALUE);
+VALUE ossl_to_der_if_possible(VALUE);
+
+/*
+ * Debug
+ */
+extern VALUE dOSSL;
+
+#if defined(HAVE_VA_ARGS_MACRO)
+#define OSSL_Debug(...) do { \
+  if (dOSSL == Qtrue) { \
+    fprintf(stderr, "OSSL_DEBUG: "); \
+    fprintf(stderr, __VA_ARGS__); \
+    fprintf(stderr, " [%s:%d]\n", __FILE__, __LINE__); \
+  } \
+} while (0)
+
+#define OSSL_Warning(fmt, ...) do { \
+  OSSL_Debug((fmt), ##__VA_ARGS__); \
+  rb_warning((fmt), ##__VA_ARGS__); \
+} while (0)
+
+#define OSSL_Warn(fmt, ...) do { \
+  OSSL_Debug((fmt), ##__VA_ARGS__); \
+  rb_warn((fmt), ##__VA_ARGS__); \
+} while (0)
+#else
+void ossl_debug(const char *, ...);
+#define OSSL_Debug ossl_debug
+#define OSSL_Warning rb_warning
+#define OSSL_Warn rb_warn
+#endif
+
+/*
+ * Include all parts
+ */
+#include "openssl_missing.h"
+#include "ruby_missing.h"
+#include "ossl_asn1.h"
+#include "ossl_bio.h"
+#include "ossl_bn.h"
+#include "ossl_cipher.h"
+#include "ossl_config.h"
+#include "ossl_digest.h"
+#include "ossl_hmac.h"
+#include "ossl_ns_spki.h"
+#include "ossl_ocsp.h"
+#include "ossl_pkcs12.h"
+#include "ossl_pkcs7.h"
+#include "ossl_pkcs5.h"
+#include "ossl_pkey.h"
+#include "ossl_rand.h"
+#include "ossl_ssl.h"
+#include "ossl_version.h"
+#include "ossl_x509.h"
+#include "ossl_engine.h"
+
+void Init_openssl(void);
+
+#endif /* _OSSL_H_ */
diff --git a/ext/openssl/ossl_asn1.c b/ext/openssl/ossl_asn1.c
new file mode 100644
index 0000000..1d3ee4a
--- /dev/null
+++ b/ext/openssl/ossl_asn1.c
@@ -0,0 +1,1938 @@
+/*
+ * 'OpenSSL for Ruby' team members
+ * Copyright (C) 2003
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#include "ossl.h"
+
+static VALUE join_der(VALUE enumerable);
+static VALUE ossl_asn1_decode0(unsigned char **pp, long length, long *offset,
+			       int depth, int yield, long *num_read);
+static VALUE ossl_asn1_initialize(int argc, VALUE *argv, VALUE self);
+static VALUE ossl_asn1eoc_initialize(VALUE self);
+
+/*
+ * DATE conversion
+ */
+VALUE
+asn1time_to_time(const ASN1_TIME *time)
+{
+    struct tm tm;
+    VALUE argv[6];
+    int count;
+
+    if (!time || !time->data) return Qnil;
+    memset(&tm, 0, sizeof(struct tm));
+
+    switch (time->type) {
+    case V_ASN1_UTCTIME:
+	count = sscanf((const char *)time->data, "%2d%2d%2d%2d%2d%2dZ",
+		&tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min,
+		&tm.tm_sec);
+
+	if (count == 5) {
+	    tm.tm_sec = 0;
+	} else if (count != 6) {
+	    ossl_raise(rb_eTypeError, "bad UTCTIME format: \"%s\"",
+		    time->data);
+	}
+	if (tm.tm_year < 69) {
+	    tm.tm_year += 2000;
+	} else {
+	    tm.tm_year += 1900;
+	}
+	break;
+    case V_ASN1_GENERALIZEDTIME:
+	count = sscanf((const char *)time->data, "%4d%2d%2d%2d%2d%2dZ",
+		&tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min,
+		&tm.tm_sec);
+	if (count == 5) {
+		tm.tm_sec = 0;
+	}
+	else if (count != 6) {
+		ossl_raise(rb_eTypeError, "bad GENERALIZEDTIME format: \"%s\"",
+			time->data);
+	}
+	break;
+    default:
+	rb_warning("unknown time format");
+        return Qnil;
+    }
+    argv[0] = INT2NUM(tm.tm_year);
+    argv[1] = INT2NUM(tm.tm_mon);
+    argv[2] = INT2NUM(tm.tm_mday);
+    argv[3] = INT2NUM(tm.tm_hour);
+    argv[4] = INT2NUM(tm.tm_min);
+    argv[5] = INT2NUM(tm.tm_sec);
+
+    return rb_funcall2(rb_cTime, rb_intern("utc"), 6, argv);
+}
+
+#if defined(HAVE_ASN1_TIME_ADJ)
+void
+ossl_time_split(VALUE time, time_t *sec, int *days)
+{
+    VALUE num = rb_Integer(time);
+
+    if (FIXNUM_P(num)) {
+	time_t t = FIX2LONG(num);
+	*sec = t % 86400;
+	*days = rb_long2int(t / 86400);
+    }
+    else {
+	*days = NUM2INT(rb_funcall(num, rb_intern("/"), 1, INT2FIX(86400)));
+	*sec = NUM2TIMET(rb_funcall(num, rb_intern("%"), 1, INT2FIX(86400)));
+    }
+}
+#else
+time_t
+time_to_time_t(VALUE time)
+{
+    return (time_t)NUM2TIMET(rb_Integer(time));
+}
+#endif
+
+/*
+ * STRING conversion
+ */
+VALUE
+asn1str_to_str(const ASN1_STRING *str)
+{
+    return rb_str_new((const char *)str->data, str->length);
+}
+
+/*
+ * ASN1_INTEGER conversions
+ */
+VALUE
+asn1integer_to_num(const ASN1_INTEGER *ai)
+{
+    BIGNUM *bn;
+    VALUE num;
+
+    if (!ai) {
+	ossl_raise(rb_eTypeError, "ASN1_INTEGER is NULL!");
+    }
+    if (ai->type == V_ASN1_ENUMERATED)
+	/* const_cast: workaround for old OpenSSL */
+	bn = ASN1_ENUMERATED_to_BN((ASN1_ENUMERATED *)ai, NULL);
+    else
+	bn = ASN1_INTEGER_to_BN(ai, NULL);
+
+    if (!bn)
+	ossl_raise(eOSSLError, NULL);
+    num = ossl_bn_new(bn);
+    BN_free(bn);
+
+    return num;
+}
+
+ASN1_INTEGER *
+num_to_asn1integer(VALUE obj, ASN1_INTEGER *ai)
+{
+    BIGNUM *bn;
+
+    if (NIL_P(obj))
+	ossl_raise(rb_eTypeError, "Can't convert nil into Integer");
+
+    bn = GetBNPtr(obj);
+
+    if (!(ai = BN_to_ASN1_INTEGER(bn, ai)))
+	ossl_raise(eOSSLError, NULL);
+
+    return ai;
+}
+
+/********/
+/*
+ * ASN1 module
+ */
+#define ossl_asn1_get_value(o)           rb_attr_get((o),sivVALUE)
+#define ossl_asn1_get_tag(o)             rb_attr_get((o),sivTAG)
+#define ossl_asn1_get_tagging(o)         rb_attr_get((o),sivTAGGING)
+#define ossl_asn1_get_tag_class(o)       rb_attr_get((o),sivTAG_CLASS)
+#define ossl_asn1_get_infinite_length(o) rb_attr_get((o),sivINFINITE_LENGTH)
+
+#define ossl_asn1_set_value(o,v)           rb_ivar_set((o),sivVALUE,(v))
+#define ossl_asn1_set_tag(o,v)             rb_ivar_set((o),sivTAG,(v))
+#define ossl_asn1_set_tagging(o,v)         rb_ivar_set((o),sivTAGGING,(v))
+#define ossl_asn1_set_tag_class(o,v)       rb_ivar_set((o),sivTAG_CLASS,(v))
+#define ossl_asn1_set_infinite_length(o,v) rb_ivar_set((o),sivINFINITE_LENGTH,(v))
+
+VALUE mASN1;
+VALUE eASN1Error;
+
+VALUE cASN1Data;
+VALUE cASN1Primitive;
+VALUE cASN1Constructive;
+
+VALUE cASN1EndOfContent;
+VALUE cASN1Boolean;                           /* BOOLEAN           */
+VALUE cASN1Integer, cASN1Enumerated;          /* INTEGER           */
+VALUE cASN1BitString;                         /* BIT STRING        */
+VALUE cASN1OctetString, cASN1UTF8String;      /* STRINGs           */
+VALUE cASN1NumericString, cASN1PrintableString;
+VALUE cASN1T61String, cASN1VideotexString;
+VALUE cASN1IA5String, cASN1GraphicString;
+VALUE cASN1ISO64String, cASN1GeneralString;
+VALUE cASN1UniversalString, cASN1BMPString;
+VALUE cASN1Null;                              /* NULL              */
+VALUE cASN1ObjectId;                          /* OBJECT IDENTIFIER */
+VALUE cASN1UTCTime, cASN1GeneralizedTime;     /* TIME              */
+VALUE cASN1Sequence, cASN1Set;                /* CONSTRUCTIVE      */
+
+static VALUE sym_IMPLICIT, sym_EXPLICIT;
+static VALUE sym_UNIVERSAL, sym_APPLICATION, sym_CONTEXT_SPECIFIC, sym_PRIVATE;
+static ID sivVALUE, sivTAG, sivTAG_CLASS, sivTAGGING, sivINFINITE_LENGTH, sivUNUSED_BITS;
+static ID id_each;
+
+/*
+ * Ruby to ASN1 converters
+ */
+static ASN1_BOOLEAN
+obj_to_asn1bool(VALUE obj)
+{
+    if (NIL_P(obj))
+	ossl_raise(rb_eTypeError, "Can't convert nil into Boolean");
+
+     return RTEST(obj) ? 0xff : 0x0;
+}
+
+static ASN1_INTEGER*
+obj_to_asn1int(VALUE obj)
+{
+    return num_to_asn1integer(obj, NULL);
+}
+
+static ASN1_BIT_STRING*
+obj_to_asn1bstr(VALUE obj, long unused_bits)
+{
+    ASN1_BIT_STRING *bstr;
+
+    if(unused_bits < 0) unused_bits = 0;
+    StringValue(obj);
+    if(!(bstr = ASN1_BIT_STRING_new()))
+	ossl_raise(eASN1Error, NULL);
+    ASN1_BIT_STRING_set(bstr, (unsigned char *)RSTRING_PTR(obj), RSTRING_LENINT(obj));
+    bstr->flags &= ~(ASN1_STRING_FLAG_BITS_LEFT|0x07); /* clear */
+    bstr->flags |= ASN1_STRING_FLAG_BITS_LEFT|(unused_bits&0x07);
+
+    return bstr;
+}
+
+static ASN1_STRING*
+obj_to_asn1str(VALUE obj)
+{
+    ASN1_STRING *str;
+
+    StringValue(obj);
+    if(!(str = ASN1_STRING_new()))
+	ossl_raise(eASN1Error, NULL);
+    ASN1_STRING_set(str, RSTRING_PTR(obj), RSTRING_LENINT(obj));
+
+    return str;
+}
+
+static ASN1_NULL*
+obj_to_asn1null(VALUE obj)
+{
+    ASN1_NULL *null;
+
+    if(!NIL_P(obj))
+	ossl_raise(eASN1Error, "nil expected");
+    if(!(null = ASN1_NULL_new()))
+	ossl_raise(eASN1Error, NULL);
+
+    return null;
+}
+
+static ASN1_OBJECT*
+obj_to_asn1obj(VALUE obj)
+{
+    ASN1_OBJECT *a1obj;
+
+    StringValueCStr(obj);
+    a1obj = OBJ_txt2obj(RSTRING_PTR(obj), 0);
+    if(!a1obj) a1obj = OBJ_txt2obj(RSTRING_PTR(obj), 1);
+    if(!a1obj) ossl_raise(eASN1Error, "invalid OBJECT ID %"PRIsVALUE, obj);
+
+    return a1obj;
+}
+
+static ASN1_UTCTIME *
+obj_to_asn1utime(VALUE time)
+{
+    time_t sec;
+    ASN1_UTCTIME *t;
+
+#if defined(HAVE_ASN1_TIME_ADJ)
+    int off_days;
+
+    ossl_time_split(time, &sec, &off_days);
+    if (!(t = ASN1_UTCTIME_adj(NULL, sec, off_days, 0)))
+#else
+    sec = time_to_time_t(time);
+    if (!(t = ASN1_UTCTIME_set(NULL, sec)))
+#endif
+	ossl_raise(eASN1Error, NULL);
+
+    return t;
+}
+
+static ASN1_GENERALIZEDTIME *
+obj_to_asn1gtime(VALUE time)
+{
+    time_t sec;
+    ASN1_GENERALIZEDTIME *t;
+
+#if defined(HAVE_ASN1_TIME_ADJ)
+    int off_days;
+
+    ossl_time_split(time, &sec, &off_days);
+    if (!(t = ASN1_GENERALIZEDTIME_adj(NULL, sec, off_days, 0)))
+#else
+    sec = time_to_time_t(time);
+    if (!(t = ASN1_GENERALIZEDTIME_set(NULL, sec)))
+#endif
+	ossl_raise(eASN1Error, NULL);
+
+    return t;
+}
+
+static ASN1_STRING*
+obj_to_asn1derstr(VALUE obj)
+{
+    ASN1_STRING *a1str;
+    VALUE str;
+
+    str = ossl_to_der(obj);
+    if(!(a1str = ASN1_STRING_new()))
+	ossl_raise(eASN1Error, NULL);
+    ASN1_STRING_set(a1str, RSTRING_PTR(str), RSTRING_LENINT(str));
+
+    return a1str;
+}
+
+/*
+ * DER to Ruby converters
+ */
+static VALUE
+decode_bool(unsigned char* der, long length)
+{
+    const unsigned char *p = der;
+
+    if (length != 3)
+	ossl_raise(eASN1Error, "invalid length for BOOLEAN");
+    if (p[0] != 1 || p[1] != 1)
+	ossl_raise(eASN1Error, "invalid BOOLEAN");
+
+    return p[2] ? Qtrue : Qfalse;
+}
+
+static VALUE
+decode_int(unsigned char* der, long length)
+{
+    ASN1_INTEGER *ai;
+    const unsigned char *p;
+    VALUE ret;
+    int status = 0;
+
+    p = der;
+    if(!(ai = d2i_ASN1_INTEGER(NULL, &p, length)))
+	ossl_raise(eASN1Error, NULL);
+    ret = rb_protect((VALUE (*)(VALUE))asn1integer_to_num,
+		     (VALUE)ai, &status);
+    ASN1_INTEGER_free(ai);
+    if(status) rb_jump_tag(status);
+
+    return ret;
+}
+
+static VALUE
+decode_bstr(unsigned char* der, long length, long *unused_bits)
+{
+    ASN1_BIT_STRING *bstr;
+    const unsigned char *p;
+    long len;
+    VALUE ret;
+
+    p = der;
+    if(!(bstr = d2i_ASN1_BIT_STRING(NULL, &p, length)))
+	ossl_raise(eASN1Error, NULL);
+    len = bstr->length;
+    *unused_bits = 0;
+    if(bstr->flags & ASN1_STRING_FLAG_BITS_LEFT)
+	*unused_bits = bstr->flags & 0x07;
+    ret = rb_str_new((const char *)bstr->data, len);
+    ASN1_BIT_STRING_free(bstr);
+
+    return ret;
+}
+
+static VALUE
+decode_enum(unsigned char* der, long length)
+{
+    ASN1_ENUMERATED *ai;
+    const unsigned char *p;
+    VALUE ret;
+    int status = 0;
+
+    p = der;
+    if(!(ai = d2i_ASN1_ENUMERATED(NULL, &p, length)))
+	ossl_raise(eASN1Error, NULL);
+    ret = rb_protect((VALUE (*)(VALUE))asn1integer_to_num,
+		     (VALUE)ai, &status);
+    ASN1_ENUMERATED_free(ai);
+    if(status) rb_jump_tag(status);
+
+    return ret;
+}
+
+static VALUE
+decode_null(unsigned char* der, long length)
+{
+    ASN1_NULL *null;
+    const unsigned char *p;
+
+    p = der;
+    if(!(null = d2i_ASN1_NULL(NULL, &p, length)))
+	ossl_raise(eASN1Error, NULL);
+    ASN1_NULL_free(null);
+
+    return Qnil;
+}
+
+static VALUE
+decode_obj(unsigned char* der, long length)
+{
+    ASN1_OBJECT *obj;
+    const unsigned char *p;
+    VALUE ret;
+    int nid;
+    BIO *bio;
+
+    p = der;
+    if(!(obj = d2i_ASN1_OBJECT(NULL, &p, length)))
+	ossl_raise(eASN1Error, NULL);
+    if((nid = OBJ_obj2nid(obj)) != NID_undef){
+	ASN1_OBJECT_free(obj);
+	ret = rb_str_new2(OBJ_nid2sn(nid));
+    }
+    else{
+	if(!(bio = BIO_new(BIO_s_mem()))){
+	    ASN1_OBJECT_free(obj);
+	    ossl_raise(eASN1Error, NULL);
+	}
+	i2a_ASN1_OBJECT(bio, obj);
+	ASN1_OBJECT_free(obj);
+	ret = ossl_membio2str(bio);
+    }
+
+    return ret;
+}
+
+static VALUE
+decode_time(unsigned char* der, long length)
+{
+    ASN1_TIME *time;
+    const unsigned char *p;
+    VALUE ret;
+    int status = 0;
+
+    p = der;
+    if(!(time = d2i_ASN1_TIME(NULL, &p, length)))
+	ossl_raise(eASN1Error, NULL);
+    ret = rb_protect((VALUE (*)(VALUE))asn1time_to_time,
+		     (VALUE)time, &status);
+    ASN1_TIME_free(time);
+    if(status) rb_jump_tag(status);
+
+    return ret;
+}
+
+static VALUE
+decode_eoc(unsigned char *der, long length)
+{
+    if (length != 2 || !(der[0] == 0x00 && der[1] == 0x00))
+	ossl_raise(eASN1Error, NULL);
+
+    return rb_str_new("", 0);
+}
+
+/********/
+
+typedef struct {
+    const char *name;
+    VALUE *klass;
+} ossl_asn1_info_t;
+
+static const ossl_asn1_info_t ossl_asn1_info[] = {
+    { "EOC",               &cASN1EndOfContent,    },  /*  0 */
+    { "BOOLEAN",           &cASN1Boolean,         },  /*  1 */
+    { "INTEGER",           &cASN1Integer,         },  /*  2 */
+    { "BIT_STRING",        &cASN1BitString,       },  /*  3 */
+    { "OCTET_STRING",      &cASN1OctetString,     },  /*  4 */
+    { "NULL",              &cASN1Null,            },  /*  5 */
+    { "OBJECT",            &cASN1ObjectId,        },  /*  6 */
+    { "OBJECT_DESCRIPTOR", NULL,                  },  /*  7 */
+    { "EXTERNAL",          NULL,                  },  /*  8 */
+    { "REAL",              NULL,                  },  /*  9 */
+    { "ENUMERATED",        &cASN1Enumerated,      },  /* 10 */
+    { "EMBEDDED_PDV",      NULL,                  },  /* 11 */
+    { "UTF8STRING",        &cASN1UTF8String,      },  /* 12 */
+    { "RELATIVE_OID",      NULL,                  },  /* 13 */
+    { "[UNIVERSAL 14]",    NULL,                  },  /* 14 */
+    { "[UNIVERSAL 15]",    NULL,                  },  /* 15 */
+    { "SEQUENCE",          &cASN1Sequence,        },  /* 16 */
+    { "SET",               &cASN1Set,             },  /* 17 */
+    { "NUMERICSTRING",     &cASN1NumericString,   },  /* 18 */
+    { "PRINTABLESTRING",   &cASN1PrintableString, },  /* 19 */
+    { "T61STRING",         &cASN1T61String,       },  /* 20 */
+    { "VIDEOTEXSTRING",    &cASN1VideotexString,  },  /* 21 */
+    { "IA5STRING",         &cASN1IA5String,       },  /* 22 */
+    { "UTCTIME",           &cASN1UTCTime,         },  /* 23 */
+    { "GENERALIZEDTIME",   &cASN1GeneralizedTime, },  /* 24 */
+    { "GRAPHICSTRING",     &cASN1GraphicString,   },  /* 25 */
+    { "ISO64STRING",       &cASN1ISO64String,     },  /* 26 */
+    { "GENERALSTRING",     &cASN1GeneralString,   },  /* 27 */
+    { "UNIVERSALSTRING",   &cASN1UniversalString, },  /* 28 */
+    { "CHARACTER_STRING",  NULL,                  },  /* 29 */
+    { "BMPSTRING",         &cASN1BMPString,       },  /* 30 */
+};
+
+enum {ossl_asn1_info_size = (sizeof(ossl_asn1_info)/sizeof(ossl_asn1_info[0]))};
+
+static VALUE class_tag_map;
+
+static int ossl_asn1_default_tag(VALUE obj);
+
+ASN1_TYPE*
+ossl_asn1_get_asn1type(VALUE obj)
+{
+    ASN1_TYPE *ret;
+    VALUE value, rflag;
+    void *ptr;
+    void (*free_func)();
+    int tag, flag;
+
+    tag = ossl_asn1_default_tag(obj);
+    value = ossl_asn1_get_value(obj);
+    switch(tag){
+    case V_ASN1_BOOLEAN:
+	ptr = (void*)(VALUE)obj_to_asn1bool(value);
+	free_func = NULL;
+	break;
+    case V_ASN1_INTEGER:         /* FALLTHROUGH */
+    case V_ASN1_ENUMERATED:
+	ptr = obj_to_asn1int(value);
+	free_func = ASN1_INTEGER_free;
+	break;
+    case V_ASN1_BIT_STRING:
+        rflag = rb_attr_get(obj, sivUNUSED_BITS);
+        flag = NIL_P(rflag) ? -1 : NUM2INT(rflag);
+	ptr = obj_to_asn1bstr(value, flag);
+	free_func = ASN1_BIT_STRING_free;
+	break;
+    case V_ASN1_NULL:
+	ptr = obj_to_asn1null(value);
+	free_func = ASN1_NULL_free;
+	break;
+    case V_ASN1_OCTET_STRING:    /* FALLTHROUGH */
+    case V_ASN1_UTF8STRING:      /* FALLTHROUGH */
+    case V_ASN1_NUMERICSTRING:   /* FALLTHROUGH */
+    case V_ASN1_PRINTABLESTRING: /* FALLTHROUGH */
+    case V_ASN1_T61STRING:       /* FALLTHROUGH */
+    case V_ASN1_VIDEOTEXSTRING:  /* FALLTHROUGH */
+    case V_ASN1_IA5STRING:       /* FALLTHROUGH */
+    case V_ASN1_GRAPHICSTRING:   /* FALLTHROUGH */
+    case V_ASN1_ISO64STRING:     /* FALLTHROUGH */
+    case V_ASN1_GENERALSTRING:   /* FALLTHROUGH */
+    case V_ASN1_UNIVERSALSTRING: /* FALLTHROUGH */
+    case V_ASN1_BMPSTRING:
+	ptr = obj_to_asn1str(value);
+	free_func = ASN1_STRING_free;
+	break;
+    case V_ASN1_OBJECT:
+	ptr = obj_to_asn1obj(value);
+	free_func = ASN1_OBJECT_free;
+	break;
+    case V_ASN1_UTCTIME:
+	ptr = obj_to_asn1utime(value);
+	free_func = ASN1_TIME_free;
+	break;
+    case V_ASN1_GENERALIZEDTIME:
+	ptr = obj_to_asn1gtime(value);
+	free_func = ASN1_TIME_free;
+	break;
+    case V_ASN1_SET:             /* FALLTHROUGH */
+    case V_ASN1_SEQUENCE:
+	ptr = obj_to_asn1derstr(obj);
+	free_func = ASN1_STRING_free;
+	break;
+    default:
+	ossl_raise(eASN1Error, "unsupported ASN.1 type");
+    }
+    if(!(ret = OPENSSL_malloc(sizeof(ASN1_TYPE)))){
+	if(free_func) free_func(ptr);
+	ossl_raise(eASN1Error, "ASN1_TYPE alloc failure");
+    }
+    memset(ret, 0, sizeof(ASN1_TYPE));
+    ASN1_TYPE_set(ret, tag, ptr);
+
+    return ret;
+}
+
+static int
+ossl_asn1_default_tag(VALUE obj)
+{
+    VALUE tmp_class, tag;
+
+    tmp_class = CLASS_OF(obj);
+    while (!NIL_P(tmp_class)) {
+	tag = rb_hash_lookup(class_tag_map, tmp_class);
+	if (tag != Qnil)
+	    return NUM2INT(tag);
+	tmp_class = rb_class_superclass(tmp_class);
+    }
+    ossl_raise(eASN1Error, "universal tag for %"PRIsVALUE" not found",
+	       rb_obj_class(obj));
+}
+
+static int
+ossl_asn1_tag(VALUE obj)
+{
+    VALUE tag;
+
+    tag = ossl_asn1_get_tag(obj);
+    if(NIL_P(tag))
+	ossl_raise(eASN1Error, "tag number not specified");
+
+    return NUM2INT(tag);
+}
+
+static int
+ossl_asn1_is_explicit(VALUE obj)
+{
+    VALUE s;
+
+    s = ossl_asn1_get_tagging(obj);
+    if (NIL_P(s) || s == sym_IMPLICIT)
+	return 0;
+    else if (s == sym_EXPLICIT)
+	return 1;
+    else
+	ossl_raise(eASN1Error, "invalid tag default");
+}
+
+static int
+ossl_asn1_tag_class(VALUE obj)
+{
+    VALUE s;
+
+    s = ossl_asn1_get_tag_class(obj);
+    if (NIL_P(s) || s == sym_UNIVERSAL)
+	return V_ASN1_UNIVERSAL;
+    else if (s == sym_APPLICATION)
+	return V_ASN1_APPLICATION;
+    else if (s == sym_CONTEXT_SPECIFIC)
+	return V_ASN1_CONTEXT_SPECIFIC;
+    else if (s == sym_PRIVATE)
+	return V_ASN1_PRIVATE;
+    else
+	ossl_raise(eASN1Error, "invalid tag class");
+}
+
+static VALUE
+ossl_asn1_class2sym(int tc)
+{
+    if((tc & V_ASN1_PRIVATE) == V_ASN1_PRIVATE)
+	return sym_PRIVATE;
+    else if((tc & V_ASN1_CONTEXT_SPECIFIC) == V_ASN1_CONTEXT_SPECIFIC)
+	return sym_CONTEXT_SPECIFIC;
+    else if((tc & V_ASN1_APPLICATION) == V_ASN1_APPLICATION)
+	return sym_APPLICATION;
+    else
+	return sym_UNIVERSAL;
+}
+
+/*
+ * call-seq:
+ *    OpenSSL::ASN1::ASN1Data.new(value, tag, tag_class) => ASN1Data
+ *
+ * +value+: Please have a look at Constructive and Primitive to see how Ruby
+ * types are mapped to ASN.1 types and vice versa.
+ *
+ * +tag+: A +Number+ indicating the tag number.
+ *
+ * +tag_class+: A +Symbol+ indicating the tag class. Please cf. ASN1 for
+ * possible values.
+ *
+ * == Example
+ *   asn1_int = OpenSSL::ASN1Data.new(42, 2, :UNIVERSAL) # => Same as OpenSSL::ASN1::Integer.new(42)
+ *   tagged_int = OpenSSL::ASN1Data.new(42, 0, :CONTEXT_SPECIFIC) # implicitly 0-tagged INTEGER
+ */
+static VALUE
+ossl_asn1data_initialize(VALUE self, VALUE value, VALUE tag, VALUE tag_class)
+{
+    if(!SYMBOL_P(tag_class))
+	ossl_raise(eASN1Error, "invalid tag class");
+    if (tag_class == sym_UNIVERSAL && NUM2INT(tag) > 31)
+	ossl_raise(eASN1Error, "tag number for Universal too large");
+    ossl_asn1_set_tag(self, tag);
+    ossl_asn1_set_value(self, value);
+    ossl_asn1_set_tag_class(self, tag_class);
+    ossl_asn1_set_infinite_length(self, Qfalse);
+
+    return self;
+}
+
+static VALUE
+join_der_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, str))
+{
+    i = ossl_to_der_if_possible(i);
+    StringValue(i);
+    rb_str_append(str, i);
+    return Qnil;
+}
+
+static VALUE
+join_der(VALUE enumerable)
+{
+    VALUE str = rb_str_new(0, 0);
+    rb_block_call(enumerable, id_each, 0, 0, join_der_i, str);
+    return str;
+}
+
+/*
+ * call-seq:
+ *    asn1.to_der => DER-encoded String
+ *
+ * Encodes this ASN1Data into a DER-encoded String value. The result is
+ * DER-encoded except for the possibility of infinite length encodings.
+ * Infinite length encodings are not allowed in strict DER, so strictly
+ * speaking the result of such an encoding would be a BER-encoding.
+ */
+static VALUE
+ossl_asn1data_to_der(VALUE self)
+{
+    VALUE value, der, inf_length;
+    int tag, tag_class, is_cons = 0;
+    long length;
+    unsigned char *p;
+
+    value = ossl_asn1_get_value(self);
+    if(rb_obj_is_kind_of(value, rb_cArray)){
+	is_cons = 1;
+	value = join_der(value);
+    }
+    StringValue(value);
+
+    tag = ossl_asn1_tag(self);
+    tag_class = ossl_asn1_tag_class(self);
+    inf_length = ossl_asn1_get_infinite_length(self);
+    if (inf_length == Qtrue) {
+	is_cons = 2;
+    }
+    if((length = ASN1_object_size(is_cons, RSTRING_LENINT(value), tag)) <= 0)
+	ossl_raise(eASN1Error, NULL);
+    der = rb_str_new(0, length);
+    p = (unsigned char *)RSTRING_PTR(der);
+    ASN1_put_object(&p, is_cons, RSTRING_LENINT(value), tag, tag_class);
+    memcpy(p, RSTRING_PTR(value), RSTRING_LEN(value));
+    p += RSTRING_LEN(value);
+    ossl_str_adjust(der, p);
+
+    return der;
+}
+
+static VALUE
+int_ossl_asn1_decode0_prim(unsigned char **pp, long length, long hlen, int tag,
+			   VALUE tc, long *num_read)
+{
+    VALUE value, asn1data;
+    unsigned char *p;
+    long flag = 0;
+
+    p = *pp;
+
+    if(tc == sym_UNIVERSAL && tag < ossl_asn1_info_size) {
+	switch(tag){
+	case V_ASN1_EOC:
+	    value = decode_eoc(p, hlen+length);
+	    break;
+	case V_ASN1_BOOLEAN:
+	    value = decode_bool(p, hlen+length);
+	    break;
+	case V_ASN1_INTEGER:
+	    value = decode_int(p, hlen+length);
+	    break;
+	case V_ASN1_BIT_STRING:
+	    value = decode_bstr(p, hlen+length, &flag);
+	    break;
+	case V_ASN1_NULL:
+	    value = decode_null(p, hlen+length);
+	    break;
+	case V_ASN1_ENUMERATED:
+	    value = decode_enum(p, hlen+length);
+	    break;
+	case V_ASN1_OBJECT:
+	    value = decode_obj(p, hlen+length);
+	    break;
+	case V_ASN1_UTCTIME:           /* FALLTHROUGH */
+	case V_ASN1_GENERALIZEDTIME:
+	    value = decode_time(p, hlen+length);
+	    break;
+	default:
+	    /* use original value */
+	    p += hlen;
+	    value = rb_str_new((const char *)p, length);
+	    break;
+	}
+    }
+    else {
+	p += hlen;
+	value = rb_str_new((const char *)p, length);
+    }
+
+    *pp += hlen + length;
+    *num_read = hlen + length;
+
+    if (tc == sym_UNIVERSAL &&
+	tag < ossl_asn1_info_size && ossl_asn1_info[tag].klass) {
+	VALUE klass = *ossl_asn1_info[tag].klass;
+	VALUE args[4];
+	args[0] = value;
+	args[1] = INT2NUM(tag);
+	args[2] = Qnil;
+	args[3] = tc;
+	asn1data = rb_obj_alloc(klass);
+	ossl_asn1_initialize(4, args, asn1data);
+	if(tag == V_ASN1_BIT_STRING){
+	    rb_ivar_set(asn1data, sivUNUSED_BITS, LONG2NUM(flag));
+	}
+    }
+    else {
+	asn1data = rb_obj_alloc(cASN1Data);
+	ossl_asn1data_initialize(asn1data, value, INT2NUM(tag), tc);
+    }
+
+    return asn1data;
+}
+
+static VALUE
+int_ossl_asn1_decode0_cons(unsigned char **pp, long max_len, long length,
+			   long *offset, int depth, int yield, int j,
+			   int tag, VALUE tc, long *num_read)
+{
+    VALUE value, asn1data, ary;
+    int infinite;
+    long available_len, off = *offset;
+
+    infinite = (j == 0x21);
+    ary = rb_ary_new();
+
+    available_len = infinite ? max_len : length;
+    while (available_len > 0) {
+	long inner_read = 0;
+	value = ossl_asn1_decode0(pp, available_len, &off, depth + 1, yield, &inner_read);
+	*num_read += inner_read;
+	available_len -= inner_read;
+	rb_ary_push(ary, value);
+
+	if (infinite &&
+	    NUM2INT(ossl_asn1_get_tag(value)) == V_ASN1_EOC &&
+	    ossl_asn1_get_tag_class(value) == sym_UNIVERSAL) {
+	    break;
+	}
+    }
+
+    if (tc == sym_UNIVERSAL) {
+	VALUE args[4];
+	int not_sequence_or_set;
+
+	not_sequence_or_set = tag != V_ASN1_SEQUENCE && tag != V_ASN1_SET;
+
+	if (not_sequence_or_set) {
+	    if (infinite) {
+		asn1data = rb_obj_alloc(cASN1Constructive);
+	    }
+	    else {
+		ossl_raise(eASN1Error, "invalid non-infinite tag");
+		return Qnil;
+	    }
+	}
+	else {
+	    VALUE klass = *ossl_asn1_info[tag].klass;
+	    asn1data = rb_obj_alloc(klass);
+	}
+	args[0] = ary;
+	args[1] = INT2NUM(tag);
+	args[2] = Qnil;
+	args[3] = tc;
+	ossl_asn1_initialize(4, args, asn1data);
+    }
+    else {
+	asn1data = rb_obj_alloc(cASN1Data);
+	ossl_asn1data_initialize(asn1data, ary, INT2NUM(tag), tc);
+    }
+
+    if (infinite)
+	ossl_asn1_set_infinite_length(asn1data, Qtrue);
+    else
+	ossl_asn1_set_infinite_length(asn1data, Qfalse);
+
+    *offset = off;
+    return asn1data;
+}
+
+static VALUE
+ossl_asn1_decode0(unsigned char **pp, long length, long *offset, int depth,
+		  int yield, long *num_read)
+{
+    unsigned char *start, *p;
+    const unsigned char *p0;
+    long len = 0, inner_read = 0, off = *offset, hlen;
+    int tag, tc, j;
+    VALUE asn1data, tag_class;
+
+    p = *pp;
+    start = p;
+    p0 = p;
+    j = ASN1_get_object(&p0, &len, &tag, &tc, length);
+    p = (unsigned char *)p0;
+    if(j & 0x80) ossl_raise(eASN1Error, NULL);
+    if(len > length) ossl_raise(eASN1Error, "value is too short");
+    if((tc & V_ASN1_PRIVATE) == V_ASN1_PRIVATE)
+	tag_class = sym_PRIVATE;
+    else if((tc & V_ASN1_CONTEXT_SPECIFIC) == V_ASN1_CONTEXT_SPECIFIC)
+	tag_class = sym_CONTEXT_SPECIFIC;
+    else if((tc & V_ASN1_APPLICATION) == V_ASN1_APPLICATION)
+	tag_class = sym_APPLICATION;
+    else
+	tag_class = sym_UNIVERSAL;
+
+    hlen = p - start;
+
+    if(yield) {
+	VALUE arg = rb_ary_new();
+	rb_ary_push(arg, LONG2NUM(depth));
+	rb_ary_push(arg, LONG2NUM(*offset));
+	rb_ary_push(arg, LONG2NUM(hlen));
+	rb_ary_push(arg, LONG2NUM(len));
+	rb_ary_push(arg, (j & V_ASN1_CONSTRUCTED) ? Qtrue : Qfalse);
+	rb_ary_push(arg, ossl_asn1_class2sym(tc));
+	rb_ary_push(arg, INT2NUM(tag));
+	rb_yield(arg);
+    }
+
+    if(j & V_ASN1_CONSTRUCTED) {
+	*pp += hlen;
+	off += hlen;
+	asn1data = int_ossl_asn1_decode0_cons(pp, length - hlen, len, &off, depth, yield, j, tag, tag_class, &inner_read);
+	inner_read += hlen;
+    }
+    else {
+    	if ((j & 0x01) && (len == 0)) ossl_raise(eASN1Error, "Infinite length for primitive value");
+	asn1data = int_ossl_asn1_decode0_prim(pp, len, hlen, tag, tag_class, &inner_read);
+	off += hlen + len;
+    }
+    if (num_read)
+	*num_read = inner_read;
+    if (len != 0 && inner_read != hlen + len) {
+	ossl_raise(eASN1Error,
+		   "Type mismatch. Bytes read: %ld Bytes available: %ld",
+		   inner_read, hlen + len);
+    }
+
+    *offset = off;
+    return asn1data;
+}
+
+static void
+int_ossl_decode_sanity_check(long len, long read, long offset)
+{
+    if (len != 0 && (read != len || offset != len)) {
+	ossl_raise(eASN1Error,
+		   "Type mismatch. Total bytes read: %ld Bytes available: %ld Offset: %ld",
+		   read, len, offset);
+    }
+}
+
+/*
+ * call-seq:
+ *    OpenSSL::ASN1.traverse(asn1) -> nil
+ *
+ * If a block is given, it prints out each of the elements encountered.
+ * Block parameters are (in that order):
+ * * depth: The recursion depth, plus one with each constructed value being encountered (Number)
+ * * offset: Current byte offset (Number)
+ * * header length: Combined length in bytes of the Tag and Length headers. (Number)
+ * * length: The overall remaining length of the entire data (Number)
+ * * constructed: Whether this value is constructed or not (Boolean)
+ * * tag_class: Current tag class (Symbol)
+ * * tag: The current tag (Number)
+ *
+ * == Example
+ *   der = File.binread('asn1data.der')
+ *   OpenSSL::ASN1.traverse(der) do | depth, offset, header_len, length, constructed, tag_class, tag|
+ *     puts "Depth: #{depth} Offset: #{offset} Length: #{length}"
+ *     puts "Header length: #{header_len} Tag: #{tag} Tag class: #{tag_class} Constructed: #{constructed}"
+ *   end
+ */
+static VALUE
+ossl_asn1_traverse(VALUE self, VALUE obj)
+{
+    unsigned char *p;
+    VALUE tmp;
+    long len, read = 0, offset = 0;
+
+    obj = ossl_to_der_if_possible(obj);
+    tmp = rb_str_new4(StringValue(obj));
+    p = (unsigned char *)RSTRING_PTR(tmp);
+    len = RSTRING_LEN(tmp);
+    ossl_asn1_decode0(&p, len, &offset, 0, 1, &read);
+    RB_GC_GUARD(tmp);
+    int_ossl_decode_sanity_check(len, read, offset);
+    return Qnil;
+}
+
+/*
+ * call-seq:
+ *    OpenSSL::ASN1.decode(der) -> ASN1Data
+ *
+ * Decodes a BER- or DER-encoded value and creates an ASN1Data instance. +der+
+ * may be a +String+ or any object that features a +#to_der+ method transforming
+ * it into a BER-/DER-encoded +String+.
+ *
+ * == Example
+ *   der = File.binread('asn1data')
+ *   asn1 = OpenSSL::ASN1.decode(der)
+ */
+static VALUE
+ossl_asn1_decode(VALUE self, VALUE obj)
+{
+    VALUE ret;
+    unsigned char *p;
+    VALUE tmp;
+    long len, read = 0, offset = 0;
+
+    obj = ossl_to_der_if_possible(obj);
+    tmp = rb_str_new4(StringValue(obj));
+    p = (unsigned char *)RSTRING_PTR(tmp);
+    len = RSTRING_LEN(tmp);
+    ret = ossl_asn1_decode0(&p, len, &offset, 0, 0, &read);
+    RB_GC_GUARD(tmp);
+    int_ossl_decode_sanity_check(len, read, offset);
+    return ret;
+}
+
+/*
+ * call-seq:
+ *    OpenSSL::ASN1.decode_all(der) -> Array of ASN1Data
+ *
+ * Similar to +decode+ with the difference that +decode+ expects one
+ * distinct value represented in +der+. +decode_all+ on the contrary
+ * decodes a sequence of sequential BER/DER values lined up in +der+
+ * and returns them as an array.
+ *
+ * == Example
+ *   ders = File.binread('asn1data_seq')
+ *   asn1_ary = OpenSSL::ASN1.decode_all(ders)
+ */
+static VALUE
+ossl_asn1_decode_all(VALUE self, VALUE obj)
+{
+    VALUE ary, val;
+    unsigned char *p;
+    long len, tmp_len = 0, read = 0, offset = 0;
+    VALUE tmp;
+
+    obj = ossl_to_der_if_possible(obj);
+    tmp = rb_str_new4(StringValue(obj));
+    p = (unsigned char *)RSTRING_PTR(tmp);
+    len = RSTRING_LEN(tmp);
+    tmp_len = len;
+    ary = rb_ary_new();
+    while (tmp_len > 0) {
+	long tmp_read = 0;
+	val = ossl_asn1_decode0(&p, tmp_len, &offset, 0, 0, &tmp_read);
+	rb_ary_push(ary, val);
+	read += tmp_read;
+	tmp_len -= tmp_read;
+    }
+    RB_GC_GUARD(tmp);
+    int_ossl_decode_sanity_check(len, read, offset);
+    return ary;
+}
+
+/*
+ * call-seq:
+ *    OpenSSL::ASN1::Primitive.new( value [, tag, tagging, tag_class ]) => Primitive
+ *
+ * +value+: is mandatory.
+ *
+ * +tag+: optional, may be specified for tagged values. If no +tag+ is
+ * specified, the UNIVERSAL tag corresponding to the Primitive sub-class
+ * is used by default.
+ *
+ * +tagging+: may be used as an encoding hint to encode a value either
+ * explicitly or implicitly, see ASN1 for possible values.
+ *
+ * +tag_class+: if +tag+ and +tagging+ are +nil+ then this is set to
+ * +:UNIVERSAL+ by default. If either +tag+ or +tagging+ are set then
+ * +:CONTEXT_SPECIFIC+ is used as the default. For possible values please
+ * cf. ASN1.
+ *
+ * == Example
+ *   int = OpenSSL::ASN1::Integer.new(42)
+ *   zero_tagged_int = OpenSSL::ASN1::Integer.new(42, 0, :IMPLICIT)
+ *   private_explicit_zero_tagged_int = OpenSSL::ASN1::Integer.new(42, 0, :EXPLICIT, :PRIVATE)
+ */
+static VALUE
+ossl_asn1_initialize(int argc, VALUE *argv, VALUE self)
+{
+    VALUE value, tag, tagging, tag_class;
+
+    rb_scan_args(argc, argv, "13", &value, &tag, &tagging, &tag_class);
+    if(argc > 1){
+	if(NIL_P(tag))
+	    ossl_raise(eASN1Error, "must specify tag number");
+	if(!NIL_P(tagging) && !SYMBOL_P(tagging))
+	    ossl_raise(eASN1Error, "invalid tagging method");
+	if(NIL_P(tag_class)) {
+	    if (NIL_P(tagging))
+		tag_class = sym_UNIVERSAL;
+	    else
+		tag_class = sym_CONTEXT_SPECIFIC;
+	}
+	if(!SYMBOL_P(tag_class))
+	    ossl_raise(eASN1Error, "invalid tag class");
+	if (tagging == sym_IMPLICIT && NUM2INT(tag) > 31)
+	    ossl_raise(eASN1Error, "tag number for Universal too large");
+    }
+    else{
+	tag = INT2NUM(ossl_asn1_default_tag(self));
+	tagging = Qnil;
+	tag_class = sym_UNIVERSAL;
+    }
+    ossl_asn1_set_tag(self, tag);
+    ossl_asn1_set_value(self, value);
+    ossl_asn1_set_tagging(self, tagging);
+    ossl_asn1_set_tag_class(self, tag_class);
+    ossl_asn1_set_infinite_length(self, Qfalse);
+
+    return self;
+}
+
+static VALUE
+ossl_asn1eoc_initialize(VALUE self) {
+    VALUE tag, tagging, tag_class, value;
+    tag = INT2NUM(ossl_asn1_default_tag(self));
+    tagging = Qnil;
+    tag_class = sym_UNIVERSAL;
+    value = rb_str_new("", 0);
+    ossl_asn1_set_tag(self, tag);
+    ossl_asn1_set_value(self, value);
+    ossl_asn1_set_tagging(self, tagging);
+    ossl_asn1_set_tag_class(self, tag_class);
+    ossl_asn1_set_infinite_length(self, Qfalse);
+    return self;
+}
+
+/*
+ * call-seq:
+ *    asn1.to_der => DER-encoded String
+ *
+ * See ASN1Data#to_der for details. *
+ */
+static VALUE
+ossl_asn1prim_to_der(VALUE self)
+{
+    ASN1_TYPE *asn1;
+    int tn, tc, explicit;
+    long len, reallen;
+    unsigned char *buf, *p;
+    VALUE str;
+
+    tn = NUM2INT(ossl_asn1_get_tag(self));
+    tc = ossl_asn1_tag_class(self);
+    explicit = ossl_asn1_is_explicit(self);
+    asn1 = ossl_asn1_get_asn1type(self);
+
+    len = ASN1_object_size(1, i2d_ASN1_TYPE(asn1, NULL), tn);
+    if(!(buf = OPENSSL_malloc(len))){
+	ASN1_TYPE_free(asn1);
+	ossl_raise(eASN1Error, "cannot alloc buffer");
+    }
+    p = buf;
+    if (tc == V_ASN1_UNIVERSAL) {
+        i2d_ASN1_TYPE(asn1, &p);
+    } else if (explicit) {
+        ASN1_put_object(&p, 1, i2d_ASN1_TYPE(asn1, NULL), tn, tc);
+        i2d_ASN1_TYPE(asn1, &p);
+    } else {
+        i2d_ASN1_TYPE(asn1, &p);
+        *buf = tc | tn | (*buf & V_ASN1_CONSTRUCTED);
+    }
+    ASN1_TYPE_free(asn1);
+    reallen = p - buf;
+    assert(reallen <= len);
+    str = ossl_buf2str((char *)buf, rb_long2int(reallen)); /* buf will be free in ossl_buf2str */
+
+    return str;
+}
+
+/*
+ * call-seq:
+ *    asn1.to_der => DER-encoded String
+ *
+ * See ASN1Data#to_der for details.
+ */
+static VALUE
+ossl_asn1cons_to_der(VALUE self)
+{
+    int tag, tn, tc, explicit, constructed = 1;
+    int found_prim = 0, seq_len;
+    long length;
+    unsigned char *p;
+    VALUE value, str, inf_length;
+
+    tn = NUM2INT(ossl_asn1_get_tag(self));
+    tc = ossl_asn1_tag_class(self);
+    inf_length = ossl_asn1_get_infinite_length(self);
+    if (inf_length == Qtrue) {
+	VALUE ary, example;
+	constructed = 2;
+	if (rb_obj_class(self) == cASN1Sequence ||
+	    rb_obj_class(self) == cASN1Set) {
+	    tag = ossl_asn1_default_tag(self);
+	}
+	else { /* must be a constructive encoding of a primitive value */
+	    ary = ossl_asn1_get_value(self);
+	    if (!rb_obj_is_kind_of(ary, rb_cArray))
+		ossl_raise(eASN1Error, "Constructive value must be an Array");
+	    /* Recursively descend until a primitive value is found.
+	    The overall value of the entire constructed encoding
+	    is of the type of the first primitive encoding to be
+	    found. */
+	    while (!found_prim){
+		example = rb_ary_entry(ary, 0);
+		if (rb_obj_is_kind_of(example, cASN1Primitive)){
+		    found_prim = 1;
+		}
+		else {
+		    /* example is another ASN1Constructive */
+		    if (!rb_obj_is_kind_of(example, cASN1Constructive)){
+			ossl_raise(eASN1Error, "invalid constructed encoding");
+			return Qnil; /* dummy */
+		    }
+		    ary = ossl_asn1_get_value(example);
+		}
+	    }
+	    tag = ossl_asn1_default_tag(example);
+	}
+    }
+    else {
+	if (rb_obj_class(self) == cASN1Constructive)
+	    ossl_raise(eASN1Error, "Constructive shall only be used with infinite length");
+	tag = ossl_asn1_default_tag(self);
+    }
+    explicit = ossl_asn1_is_explicit(self);
+    value = join_der(ossl_asn1_get_value(self));
+
+    seq_len = ASN1_object_size(constructed, RSTRING_LENINT(value), tag);
+    length = ASN1_object_size(constructed, seq_len, tn);
+    str = rb_str_new(0, length);
+    p = (unsigned char *)RSTRING_PTR(str);
+    if(tc == V_ASN1_UNIVERSAL)
+	ASN1_put_object(&p, constructed, RSTRING_LENINT(value), tn, tc);
+    else{
+	if(explicit){
+	    ASN1_put_object(&p, constructed, seq_len, tn, tc);
+	    ASN1_put_object(&p, constructed, RSTRING_LENINT(value), tag, V_ASN1_UNIVERSAL);
+	}
+	else{
+	    ASN1_put_object(&p, constructed, RSTRING_LENINT(value), tn, tc);
+	}
+    }
+    memcpy(p, RSTRING_PTR(value), RSTRING_LEN(value));
+    p += RSTRING_LEN(value);
+
+    /* In this case we need an additional EOC (one for the explicit part and
+     * one for the Constructive itself. The EOC for the Constructive is
+     * supplied by the user, but that for the "explicit wrapper" must be
+     * added here.
+     */
+    if (explicit && inf_length == Qtrue) {
+	ASN1_put_eoc(&p);
+    }
+    ossl_str_adjust(str, p);
+
+    return str;
+}
+
+/*
+ * call-seq:
+ *    asn1_ary.each { |asn1| block } => asn1_ary
+ *
+ * Calls <i>block</i> once for each element in +self+, passing that element
+ * as parameter +asn1+. If no block is given, an enumerator is returned
+ * instead.
+ *
+ * == Example
+ *   asn1_ary.each do |asn1|
+ *     puts asn1
+ *   end
+ */
+static VALUE
+ossl_asn1cons_each(VALUE self)
+{
+    rb_block_call(ossl_asn1_get_value(self), id_each, 0, 0, 0, 0);
+
+    return self;
+}
+
+/*
+ * call-seq:
+ *    OpenSSL::ASN1::ObjectId.register(object_id, short_name, long_name)
+ *
+ * This adds a new ObjectId to the internal tables. Where +object_id+ is the
+ * numerical form, +short_name+ is the short name, and +long_name+ is the long
+ * name.
+ *
+ * Returns +true+ if successful. Raises an OpenSSL::ASN1::ASN1Error if it fails.
+ *
+ */
+static VALUE
+ossl_asn1obj_s_register(VALUE self, VALUE oid, VALUE sn, VALUE ln)
+{
+    StringValueCStr(oid);
+    StringValueCStr(sn);
+    StringValueCStr(ln);
+
+    if(!OBJ_create(RSTRING_PTR(oid), RSTRING_PTR(sn), RSTRING_PTR(ln)))
+	ossl_raise(eASN1Error, NULL);
+
+    return Qtrue;
+}
+
+/* Document-method: OpenSSL::ASN1::ObjectId#sn
+ *
+ * The short name of the ObjectId, as defined in <openssl/objects.h>.
+ */
+/* Document-method: OpenSSL::ASN1::ObjectId#short_name
+ *
+ * +short_name+ is an alias to +sn+
+ */
+static VALUE
+ossl_asn1obj_get_sn(VALUE self)
+{
+    VALUE val, ret = Qnil;
+    int nid;
+
+    val = ossl_asn1_get_value(self);
+    if ((nid = OBJ_txt2nid(StringValueCStr(val))) != NID_undef)
+	ret = rb_str_new2(OBJ_nid2sn(nid));
+
+    return ret;
+}
+
+/* Document-method: OpenSSL::ASN1::ObjectId#ln
+ *
+ * The long name of the ObjectId, as defined in <openssl/objects.h>.
+ */
+/* Document-method: OpenSSL::ASN1::ObjectId#long_name
+ *
+ * +long_name+ is an alias to +ln+
+ */
+static VALUE
+ossl_asn1obj_get_ln(VALUE self)
+{
+    VALUE val, ret = Qnil;
+    int nid;
+
+    val = ossl_asn1_get_value(self);
+    if ((nid = OBJ_txt2nid(StringValueCStr(val))) != NID_undef)
+	ret = rb_str_new2(OBJ_nid2ln(nid));
+
+    return ret;
+}
+
+/* Document-method: OpenSSL::ASN1::ObjectId#oid
+ *
+ * The object identifier as a +String+, e.g. "1.2.3.4.5"
+ */
+static VALUE
+ossl_asn1obj_get_oid(VALUE self)
+{
+    VALUE val;
+    ASN1_OBJECT *a1obj;
+    char buf[128];
+
+    val = ossl_asn1_get_value(self);
+    a1obj = obj_to_asn1obj(val);
+    OBJ_obj2txt(buf, sizeof(buf), a1obj, 1);
+    ASN1_OBJECT_free(a1obj);
+
+    return rb_str_new2(buf);
+}
+
+#define OSSL_ASN1_IMPL_FACTORY_METHOD(klass) \
+static VALUE ossl_asn1_##klass(int argc, VALUE *argv, VALUE self)\
+{ return rb_funcall3(cASN1##klass, rb_intern("new"), argc, argv); }
+
+OSSL_ASN1_IMPL_FACTORY_METHOD(Boolean)
+OSSL_ASN1_IMPL_FACTORY_METHOD(Integer)
+OSSL_ASN1_IMPL_FACTORY_METHOD(Enumerated)
+OSSL_ASN1_IMPL_FACTORY_METHOD(BitString)
+OSSL_ASN1_IMPL_FACTORY_METHOD(OctetString)
+OSSL_ASN1_IMPL_FACTORY_METHOD(UTF8String)
+OSSL_ASN1_IMPL_FACTORY_METHOD(NumericString)
+OSSL_ASN1_IMPL_FACTORY_METHOD(PrintableString)
+OSSL_ASN1_IMPL_FACTORY_METHOD(T61String)
+OSSL_ASN1_IMPL_FACTORY_METHOD(VideotexString)
+OSSL_ASN1_IMPL_FACTORY_METHOD(IA5String)
+OSSL_ASN1_IMPL_FACTORY_METHOD(GraphicString)
+OSSL_ASN1_IMPL_FACTORY_METHOD(ISO64String)
+OSSL_ASN1_IMPL_FACTORY_METHOD(GeneralString)
+OSSL_ASN1_IMPL_FACTORY_METHOD(UniversalString)
+OSSL_ASN1_IMPL_FACTORY_METHOD(BMPString)
+OSSL_ASN1_IMPL_FACTORY_METHOD(Null)
+OSSL_ASN1_IMPL_FACTORY_METHOD(ObjectId)
+OSSL_ASN1_IMPL_FACTORY_METHOD(UTCTime)
+OSSL_ASN1_IMPL_FACTORY_METHOD(GeneralizedTime)
+OSSL_ASN1_IMPL_FACTORY_METHOD(Sequence)
+OSSL_ASN1_IMPL_FACTORY_METHOD(Set)
+OSSL_ASN1_IMPL_FACTORY_METHOD(EndOfContent)
+
+void
+Init_ossl_asn1(void)
+{
+    VALUE ary;
+    int i;
+
+#if 0
+    mOSSL = rb_define_module("OpenSSL");
+    eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError);
+#endif
+
+    sym_UNIVERSAL = ID2SYM(rb_intern_const("UNIVERSAL"));
+    sym_CONTEXT_SPECIFIC = ID2SYM(rb_intern_const("CONTEXT_SPECIFIC"));
+    sym_APPLICATION = ID2SYM(rb_intern_const("APPLICATION"));
+    sym_PRIVATE = ID2SYM(rb_intern_const("PRIVATE"));
+    sym_EXPLICIT = ID2SYM(rb_intern_const("EXPLICIT"));
+    sym_IMPLICIT = ID2SYM(rb_intern_const("IMPLICIT"));
+
+    sivVALUE = rb_intern("@value");
+    sivTAG = rb_intern("@tag");
+    sivTAGGING = rb_intern("@tagging");
+    sivTAG_CLASS = rb_intern("@tag_class");
+    sivINFINITE_LENGTH = rb_intern("@infinite_length");
+    sivUNUSED_BITS = rb_intern("@unused_bits");
+
+    /*
+     * Document-module: OpenSSL::ASN1
+     *
+     * Abstract Syntax Notation One (or ASN.1) is a notation syntax to
+     * describe data structures and is defined in ITU-T X.680. ASN.1 itself
+     * does not mandate any encoding or parsing rules, but usually ASN.1 data
+     * structures are encoded using the Distinguished Encoding Rules (DER) or
+     * less often the Basic Encoding Rules (BER) described in ITU-T X.690. DER
+     * and BER encodings are binary Tag-Length-Value (TLV) encodings that are
+     * quite concise compared to other popular data description formats such
+     * as XML, JSON etc.
+     * ASN.1 data structures are very common in cryptographic applications,
+     * e.g. X.509 public key certificates or certificate revocation lists
+     * (CRLs) are all defined in ASN.1 and DER-encoded. ASN.1, DER and BER are
+     * the building blocks of applied cryptography.
+     * The ASN1 module provides the necessary classes that allow generation
+     * of ASN.1 data structures and the methods to encode them using a DER
+     * encoding. The decode method allows parsing arbitrary BER-/DER-encoded
+     * data to a Ruby object that can then be modified and re-encoded at will.
+     *
+     * == ASN.1 class hierarchy
+     *
+     * The base class representing ASN.1 structures is ASN1Data. ASN1Data offers
+     * attributes to read and set the +tag+, the +tag_class+ and finally the
+     * +value+ of a particular ASN.1 item. Upon parsing, any tagged values
+     * (implicit or explicit) will be represented by ASN1Data instances because
+     * their "real type" can only be determined using out-of-band information
+     * from the ASN.1 type declaration. Since this information is normally
+     * known when encoding a type, all sub-classes of ASN1Data offer an
+     * additional attribute +tagging+ that allows to encode a value implicitly
+     * (+:IMPLICIT+) or explicitly (+:EXPLICIT+).
+     *
+     * === Constructive
+     *
+     * Constructive is, as its name implies, the base class for all
+     * constructed encodings, i.e. those that consist of several values,
+     * opposed to "primitive" encodings with just one single value.
+     * Primitive values that are encoded with "infinite length" are typically
+     * constructed (their values come in multiple chunks) and are therefore
+     * represented by instances of Constructive. The value of an Constructive
+     * is always an Array.
+     *
+     * ==== ASN1::Set and ASN1::Sequence
+     *
+     * The most common constructive encodings are SETs and SEQUENCEs, which is
+     * why there are two sub-classes of Constructive representing each of
+     * them.
+     *
+     * === Primitive
+     *
+     * This is the super class of all primitive values. Primitive
+     * itself is not used when parsing ASN.1 data, all values are either
+     * instances of a corresponding sub-class of Primitive or they are
+     * instances of ASN1Data if the value was tagged implicitly or explicitly.
+     * Please cf. Primitive documentation for details on sub-classes and
+     * their respective mappings of ASN.1 data types to Ruby objects.
+     *
+     * == Possible values for +tagging+
+     *
+     * When constructing an ASN1Data object the ASN.1 type definition may
+     * require certain elements to be either implicitly or explicitly tagged.
+     * This can be achieved by setting the +tagging+ attribute manually for
+     * sub-classes of ASN1Data. Use the symbol +:IMPLICIT+ for implicit
+     * tagging and +:EXPLICIT+ if the element requires explicit tagging.
+     *
+     * == Possible values for +tag_class+
+     *
+     * It is possible to create arbitrary ASN1Data objects that also support
+     * a PRIVATE or APPLICATION tag class. Possible values for the +tag_class+
+     * attribute are:
+     * * +:UNIVERSAL+ (the default for untagged values)
+     * * +:CONTEXT_SPECIFIC+ (the default for tagged values)
+     * * +:APPLICATION+
+     * * +:PRIVATE+
+     *
+     * == Tag constants
+     *
+     * There is a constant defined for each universal tag:
+     * * OpenSSL::ASN1::EOC (0)
+     * * OpenSSL::ASN1::BOOLEAN (1)
+     * * OpenSSL::ASN1::INTEGER (2)
+     * * OpenSSL::ASN1::BIT_STRING (3)
+     * * OpenSSL::ASN1::OCTET_STRING (4)
+     * * OpenSSL::ASN1::NULL (5)
+     * * OpenSSL::ASN1::OBJECT (6)
+     * * OpenSSL::ASN1::ENUMERATED (10)
+     * * OpenSSL::ASN1::UTF8STRING (12)
+     * * OpenSSL::ASN1::SEQUENCE (16)
+     * * OpenSSL::ASN1::SET (17)
+     * * OpenSSL::ASN1::NUMERICSTRING (18)
+     * * OpenSSL::ASN1::PRINTABLESTRING (19)
+     * * OpenSSL::ASN1::T61STRING (20)
+     * * OpenSSL::ASN1::VIDEOTEXSTRING (21)
+     * * OpenSSL::ASN1::IA5STRING (22)
+     * * OpenSSL::ASN1::UTCTIME (23)
+     * * OpenSSL::ASN1::GENERALIZEDTIME (24)
+     * * OpenSSL::ASN1::GRAPHICSTRING (25)
+     * * OpenSSL::ASN1::ISO64STRING (26)
+     * * OpenSSL::ASN1::GENERALSTRING (27)
+     * * OpenSSL::ASN1::UNIVERSALSTRING (28)
+     * * OpenSSL::ASN1::BMPSTRING (30)
+     *
+     * == UNIVERSAL_TAG_NAME constant
+     *
+     * An Array that stores the name of a given tag number. These names are
+     * the same as the name of the tag constant that is additionally defined,
+     * e.g. UNIVERSAL_TAG_NAME[2] = "INTEGER" and OpenSSL::ASN1::INTEGER = 2.
+     *
+     * == Example usage
+     *
+     * === Decoding and viewing a DER-encoded file
+     *   require 'openssl'
+     *   require 'pp'
+     *   der = File.binread('data.der')
+     *   asn1 = OpenSSL::ASN1.decode(der)
+     *   pp der
+     *
+     * === Creating an ASN.1 structure and DER-encoding it
+     *   require 'openssl'
+     *   version = OpenSSL::ASN1::Integer.new(1)
+     *   # Explicitly 0-tagged implies context-specific tag class
+     *   serial = OpenSSL::ASN1::Integer.new(12345, 0, :EXPLICIT, :CONTEXT_SPECIFIC)
+     *   name = OpenSSL::ASN1::PrintableString.new('Data 1')
+     *   sequence = OpenSSL::ASN1::Sequence.new( [ version, serial, name ] )
+     *   der = sequence.to_der
+     */
+    mASN1 = rb_define_module_under(mOSSL, "ASN1");
+
+    /* Document-class: OpenSSL::ASN1::ASN1Error
+     *
+     * Generic error class for all errors raised in ASN1 and any of the
+     * classes defined in it.
+     */
+    eASN1Error = rb_define_class_under(mASN1, "ASN1Error", eOSSLError);
+    rb_define_module_function(mASN1, "traverse", ossl_asn1_traverse, 1);
+    rb_define_module_function(mASN1, "decode", ossl_asn1_decode, 1);
+    rb_define_module_function(mASN1, "decode_all", ossl_asn1_decode_all, 1);
+    ary = rb_ary_new();
+
+    /*
+     * Array storing tag names at the tag's index.
+     */
+    rb_define_const(mASN1, "UNIVERSAL_TAG_NAME", ary);
+    for(i = 0; i < ossl_asn1_info_size; i++){
+	if(ossl_asn1_info[i].name[0] == '[') continue;
+	rb_define_const(mASN1, ossl_asn1_info[i].name, INT2NUM(i));
+	rb_ary_store(ary, i, rb_str_new2(ossl_asn1_info[i].name));
+    }
+
+    /* Document-class: OpenSSL::ASN1::ASN1Data
+     *
+     * The top-level class representing any ASN.1 object. When parsed by
+     * ASN1.decode, tagged values are always represented by an instance
+     * of ASN1Data.
+     *
+     * == The role of ASN1Data for parsing tagged values
+     *
+     * When encoding an ASN.1 type it is inherently clear what original
+     * type (e.g. INTEGER, OCTET STRING etc.) this value has, regardless
+     * of its tagging.
+     * But opposed to the time an ASN.1 type is to be encoded, when parsing
+     * them it is not possible to deduce the "real type" of tagged
+     * values. This is why tagged values are generally parsed into ASN1Data
+     * instances, but with a different outcome for implicit and explicit
+     * tagging.
+     *
+     * === Example of a parsed implicitly tagged value
+     *
+     * An implicitly 1-tagged INTEGER value will be parsed as an
+     * ASN1Data with
+     * * +tag+ equal to 1
+     * * +tag_class+ equal to +:CONTEXT_SPECIFIC+
+     * * +value+ equal to a +String+ that carries the raw encoding
+     *   of the INTEGER.
+     * This implies that a subsequent decoding step is required to
+     * completely decode implicitly tagged values.
+     *
+     * === Example of a parsed explicitly tagged value
+     *
+     * An explicitly 1-tagged INTEGER value will be parsed as an
+     * ASN1Data with
+     * * +tag+ equal to 1
+     * * +tag_class+ equal to +:CONTEXT_SPECIFIC+
+     * * +value+ equal to an +Array+ with one single element, an
+     *   instance of OpenSSL::ASN1::Integer, i.e. the inner element
+     *   is the non-tagged primitive value, and the tagging is represented
+     *   in the outer ASN1Data
+     *
+     * == Example - Decoding an implicitly tagged INTEGER
+     *   int = OpenSSL::ASN1::Integer.new(1, 0, :IMPLICIT) # implicit 0-tagged
+     *   seq = OpenSSL::ASN1::Sequence.new( [int] )
+     *   der = seq.to_der
+     *   asn1 = OpenSSL::ASN1.decode(der)
+     *   # pp asn1 => #<OpenSSL::ASN1::Sequence:0x87326e0
+     *   #              @infinite_length=false,
+     *   #              @tag=16,
+     *   #              @tag_class=:UNIVERSAL,
+     *   #              @tagging=nil,
+     *   #              @value=
+     *   #                [#<OpenSSL::ASN1::ASN1Data:0x87326f4
+     *   #                   @infinite_length=false,
+     *   #                   @tag=0,
+     *   #                   @tag_class=:CONTEXT_SPECIFIC,
+     *   #                   @value="\x01">]>
+     *   raw_int = asn1.value[0]
+     *   # manually rewrite tag and tag class to make it an UNIVERSAL value
+     *   raw_int.tag = OpenSSL::ASN1::INTEGER
+     *   raw_int.tag_class = :UNIVERSAL
+     *   int2 = OpenSSL::ASN1.decode(raw_int)
+     *   puts int2.value # => 1
+     *
+     * == Example - Decoding an explicitly tagged INTEGER
+     *   int = OpenSSL::ASN1::Integer.new(1, 0, :EXPLICIT) # explicit 0-tagged
+     *   seq = OpenSSL::ASN1::Sequence.new( [int] )
+     *   der = seq.to_der
+     *   asn1 = OpenSSL::ASN1.decode(der)
+     *   # pp asn1 => #<OpenSSL::ASN1::Sequence:0x87326e0
+     *   #              @infinite_length=false,
+     *   #              @tag=16,
+     *   #              @tag_class=:UNIVERSAL,
+     *   #              @tagging=nil,
+     *   #              @value=
+     *   #                [#<OpenSSL::ASN1::ASN1Data:0x87326f4
+     *   #                   @infinite_length=false,
+     *   #                   @tag=0,
+     *   #                   @tag_class=:CONTEXT_SPECIFIC,
+     *   #                   @value=
+     *   #                     [#<OpenSSL::ASN1::Integer:0x85bf308
+     *   #                        @infinite_length=false,
+     *   #                        @tag=2,
+     *   #                        @tag_class=:UNIVERSAL
+     *   #                        @tagging=nil,
+     *   #                        @value=1>]>]>
+     *   int2 = asn1.value[0].value[0]
+     *   puts int2.value # => 1
+     */
+    cASN1Data = rb_define_class_under(mASN1, "ASN1Data", rb_cObject);
+    /*
+     * Carries the value of a ASN.1 type.
+     * Please confer Constructive and Primitive for the mappings between
+     * ASN.1 data types and Ruby classes.
+     */
+    rb_attr(cASN1Data, rb_intern("value"), 1, 1, 0);
+    /*
+     * A +Number+ representing the tag number of this ASN1Data. Never +nil+.
+     */
+    rb_attr(cASN1Data, rb_intern("tag"), 1, 1, 0);
+    /*
+     * A +Symbol+ representing the tag class of this ASN1Data. Never +nil+.
+     * See ASN1Data for possible values.
+     */
+    rb_attr(cASN1Data, rb_intern("tag_class"), 1, 1, 0);
+    /*
+     * Never +nil+. A +Boolean+ indicating whether the encoding was infinite
+     * length (in the case of parsing) or whether an infinite length encoding
+     * shall be used (in the encoding case).
+     * In DER, every value has a finite length associated with it. But in
+     * scenarios where large amounts of data need to be transferred it
+     * might be desirable to have some kind of streaming support available.
+     * For example, huge OCTET STRINGs are preferably sent in smaller-sized
+     * chunks, each at a time.
+     * This is possible in BER by setting the length bytes of an encoding
+     * to zero and by this indicating that the following value will be
+     * sent in chunks. Infinite length encodings are always constructed.
+     * The end of such a stream of chunks is indicated by sending a EOC
+     * (End of Content) tag. SETs and SEQUENCEs may use an infinite length
+     * encoding, but also primitive types such as e.g. OCTET STRINGS or
+     * BIT STRINGS may leverage this functionality (cf. ITU-T X.690).
+     */
+    rb_attr(cASN1Data, rb_intern("infinite_length"), 1, 1, 0);
+    rb_define_method(cASN1Data, "initialize", ossl_asn1data_initialize, 3);
+    rb_define_method(cASN1Data, "to_der", ossl_asn1data_to_der, 0);
+
+    /* Document-class: OpenSSL::ASN1::Primitive
+     *
+     * The parent class for all primitive encodings. Attributes are the same as
+     * for ASN1Data, with the addition of +tagging+.
+     * Primitive values can never be infinite length encodings, thus it is not
+     * possible to set the +infinite_length+ attribute for Primitive and its
+     * sub-classes.
+     *
+     * == Primitive sub-classes and their mapping to Ruby classes
+     * * OpenSSL::ASN1::EndOfContent    <=> +value+ is always +nil+
+     * * OpenSSL::ASN1::Boolean         <=> +value+ is a +Boolean+
+     * * OpenSSL::ASN1::Integer         <=> +value+ is a +Number+
+     * * OpenSSL::ASN1::BitString       <=> +value+ is a +String+
+     * * OpenSSL::ASN1::OctetString     <=> +value+ is a +String+
+     * * OpenSSL::ASN1::Null            <=> +value+ is always +nil+
+     * * OpenSSL::ASN1::Object          <=> +value+ is a +String+
+     * * OpenSSL::ASN1::Enumerated      <=> +value+ is a +Number+
+     * * OpenSSL::ASN1::UTF8String      <=> +value+ is a +String+
+     * * OpenSSL::ASN1::NumericString   <=> +value+ is a +String+
+     * * OpenSSL::ASN1::PrintableString <=> +value+ is a +String+
+     * * OpenSSL::ASN1::T61String       <=> +value+ is a +String+
+     * * OpenSSL::ASN1::VideotexString  <=> +value+ is a +String+
+     * * OpenSSL::ASN1::IA5String       <=> +value+ is a +String+
+     * * OpenSSL::ASN1::UTCTime         <=> +value+ is a +Time+
+     * * OpenSSL::ASN1::GeneralizedTime <=> +value+ is a +Time+
+     * * OpenSSL::ASN1::GraphicString   <=> +value+ is a +String+
+     * * OpenSSL::ASN1::ISO64String     <=> +value+ is a +String+
+     * * OpenSSL::ASN1::GeneralString   <=> +value+ is a +String+
+     * * OpenSSL::ASN1::UniversalString <=> +value+ is a +String+
+     * * OpenSSL::ASN1::BMPString       <=> +value+ is a +String+
+     *
+     * == OpenSSL::ASN1::BitString
+     *
+     * === Additional attributes
+     * +unused_bits+: if the underlying BIT STRING's
+     * length is a multiple of 8 then +unused_bits+ is 0. Otherwise
+     * +unused_bits+ indicates the number of bits that are to be ignored in
+     * the final octet of the +BitString+'s +value+.
+     *
+     * == OpenSSL::ASN1::ObjectId
+     *
+     * NOTE: While OpenSSL::ASN1::ObjectId.new will allocate a new ObjectId,
+     * it is not typically allocated this way, but rather that are received from
+     * parsed ASN1 encodings.
+     *
+     * === Additional attributes
+     * * +sn+: the short name as defined in <openssl/objects.h>.
+     * * +ln+: the long name as defined in <openssl/objects.h>.
+     * * +oid+: the object identifier as a +String+, e.g. "1.2.3.4.5"
+     * * +short_name+: alias for +sn+.
+     * * +long_name+: alias for +ln+.
+     *
+     * == Examples
+     * With the Exception of OpenSSL::ASN1::EndOfContent, each Primitive class
+     * constructor takes at least one parameter, the +value+.
+     *
+     * === Creating EndOfContent
+     *   eoc = OpenSSL::ASN1::EndOfContent.new
+     *
+     * === Creating any other Primitive
+     *   prim = <class>.new(value) # <class> being one of the sub-classes except EndOfContent
+     *   prim_zero_tagged_implicit = <class>.new(value, 0, :IMPLICIT)
+     *   prim_zero_tagged_explicit = <class>.new(value, 0, :EXPLICIT)
+     */
+    cASN1Primitive = rb_define_class_under(mASN1, "Primitive", cASN1Data);
+    /*
+     * May be used as a hint for encoding a value either implicitly or
+     * explicitly by setting it either to +:IMPLICIT+ or to +:EXPLICIT+.
+     * +tagging+ is not set when a ASN.1 structure is parsed using
+     * OpenSSL::ASN1.decode.
+     */
+    rb_attr(cASN1Primitive, rb_intern("tagging"), 1, 1, Qtrue);
+    rb_undef_method(cASN1Primitive, "infinite_length=");
+    rb_define_method(cASN1Primitive, "initialize", ossl_asn1_initialize, -1);
+    rb_define_method(cASN1Primitive, "to_der", ossl_asn1prim_to_der, 0);
+
+    /* Document-class: OpenSSL::ASN1::Constructive
+     *
+     * The parent class for all constructed encodings. The +value+ attribute
+     * of a Constructive is always an +Array+. Attributes are the same as
+     * for ASN1Data, with the addition of +tagging+.
+     *
+     * == SET and SEQUENCE
+     *
+     * Most constructed encodings come in the form of a SET or a SEQUENCE.
+     * These encodings are represented by one of the two sub-classes of
+     * Constructive:
+     * * OpenSSL::ASN1::Set
+     * * OpenSSL::ASN1::Sequence
+     * Please note that tagged sequences and sets are still parsed as
+     * instances of ASN1Data. Find further details on tagged values
+     * there.
+     *
+     * === Example - constructing a SEQUENCE
+     *   int = OpenSSL::ASN1::Integer.new(1)
+     *   str = OpenSSL::ASN1::PrintableString.new('abc')
+     *   sequence = OpenSSL::ASN1::Sequence.new( [ int, str ] )
+     *
+     * === Example - constructing a SET
+     *   int = OpenSSL::ASN1::Integer.new(1)
+     *   str = OpenSSL::ASN1::PrintableString.new('abc')
+     *   set = OpenSSL::ASN1::Set.new( [ int, str ] )
+     *
+     * == Infinite length primitive values
+     *
+     * The only case where Constructive is used directly is for infinite
+     * length encodings of primitive values. These encodings are always
+     * constructed, with the contents of the +value+ +Array+ being either
+     * UNIVERSAL non-infinite length partial encodings of the actual value
+     * or again constructive encodings with infinite length (i.e. infinite
+     * length primitive encodings may be constructed recursively with another
+     * infinite length value within an already infinite length value). Each
+     * partial encoding must be of the same UNIVERSAL type as the overall
+     * encoding. The value of the overall encoding consists of the
+     * concatenation of each partial encoding taken in sequence. The +value+
+     * array of the outer infinite length value must end with a
+     * OpenSSL::ASN1::EndOfContent instance.
+     *
+     * Please note that it is not possible to encode Constructive without
+     * the +infinite_length+ attribute being set to +true+, use
+     * OpenSSL::ASN1::Sequence or OpenSSL::ASN1::Set in these cases instead.
+     *
+     * === Example - Infinite length OCTET STRING
+     *   partial1 = OpenSSL::ASN1::OctetString.new("\x01")
+     *   partial2 = OpenSSL::ASN1::OctetString.new("\x02")
+     *   inf_octets = OpenSSL::ASN1::Constructive.new( [ partial1,
+     *                                                   partial2,
+     *                                                   OpenSSL::ASN1::EndOfContent.new ],
+     *                                                 OpenSSL::ASN1::OCTET_STRING,
+     *                                                 nil,
+     *                                                 :UNIVERSAL )
+     *   # The real value of inf_octets is "\x01\x02", i.e. the concatenation
+     *   # of partial1 and partial2
+     *   inf_octets.infinite_length = true
+     *   der = inf_octets.to_der
+     *   asn1 = OpenSSL::ASN1.decode(der)
+     *   puts asn1.infinite_length # => true
+     */
+    cASN1Constructive = rb_define_class_under(mASN1,"Constructive", cASN1Data);
+    rb_include_module(cASN1Constructive, rb_mEnumerable);
+    /*
+     * May be used as a hint for encoding a value either implicitly or
+     * explicitly by setting it either to +:IMPLICIT+ or to +:EXPLICIT+.
+     * +tagging+ is not set when a ASN.1 structure is parsed using
+     * OpenSSL::ASN1.decode.
+     */
+    rb_attr(cASN1Constructive, rb_intern("tagging"), 1, 1, Qtrue);
+    rb_define_method(cASN1Constructive, "initialize", ossl_asn1_initialize, -1);
+    rb_define_method(cASN1Constructive, "to_der", ossl_asn1cons_to_der, 0);
+    rb_define_method(cASN1Constructive, "each", ossl_asn1cons_each, 0);
+
+#define OSSL_ASN1_DEFINE_CLASS(name, super) \
+do{\
+    cASN1##name = rb_define_class_under(mASN1, #name, cASN1##super);\
+    rb_define_module_function(mASN1, #name, ossl_asn1_##name, -1);\
+}while(0)
+
+    OSSL_ASN1_DEFINE_CLASS(Boolean, Primitive);
+    OSSL_ASN1_DEFINE_CLASS(Integer, Primitive);
+    OSSL_ASN1_DEFINE_CLASS(Enumerated, Primitive);
+    OSSL_ASN1_DEFINE_CLASS(BitString, Primitive);
+    OSSL_ASN1_DEFINE_CLASS(OctetString, Primitive);
+    OSSL_ASN1_DEFINE_CLASS(UTF8String, Primitive);
+    OSSL_ASN1_DEFINE_CLASS(NumericString, Primitive);
+    OSSL_ASN1_DEFINE_CLASS(PrintableString, Primitive);
+    OSSL_ASN1_DEFINE_CLASS(T61String, Primitive);
+    OSSL_ASN1_DEFINE_CLASS(VideotexString, Primitive);
+    OSSL_ASN1_DEFINE_CLASS(IA5String, Primitive);
+    OSSL_ASN1_DEFINE_CLASS(GraphicString, Primitive);
+    OSSL_ASN1_DEFINE_CLASS(ISO64String, Primitive);
+    OSSL_ASN1_DEFINE_CLASS(GeneralString, Primitive);
+    OSSL_ASN1_DEFINE_CLASS(UniversalString, Primitive);
+    OSSL_ASN1_DEFINE_CLASS(BMPString, Primitive);
+    OSSL_ASN1_DEFINE_CLASS(Null, Primitive);
+    OSSL_ASN1_DEFINE_CLASS(ObjectId, Primitive);
+    OSSL_ASN1_DEFINE_CLASS(UTCTime, Primitive);
+    OSSL_ASN1_DEFINE_CLASS(GeneralizedTime, Primitive);
+
+    OSSL_ASN1_DEFINE_CLASS(Sequence, Constructive);
+    OSSL_ASN1_DEFINE_CLASS(Set, Constructive);
+
+    OSSL_ASN1_DEFINE_CLASS(EndOfContent, Data);
+
+
+    /* Document-class: OpenSSL::ASN1::ObjectId
+     *
+     * Represents the primitive object id for OpenSSL::ASN1
+     */
+#if 0
+    cASN1ObjectId = rb_define_class_under(mASN1, "ObjectId", cASN1Primitive);  /* let rdoc know */
+#endif
+    rb_define_singleton_method(cASN1ObjectId, "register", ossl_asn1obj_s_register, 3);
+    rb_define_method(cASN1ObjectId, "sn", ossl_asn1obj_get_sn, 0);
+    rb_define_method(cASN1ObjectId, "ln", ossl_asn1obj_get_ln, 0);
+    rb_define_method(cASN1ObjectId, "oid", ossl_asn1obj_get_oid, 0);
+    rb_define_alias(cASN1ObjectId, "short_name", "sn");
+    rb_define_alias(cASN1ObjectId, "long_name", "ln");
+    rb_attr(cASN1BitString, rb_intern("unused_bits"), 1, 1, 0);
+
+    rb_define_method(cASN1EndOfContent, "initialize", ossl_asn1eoc_initialize, 0);
+
+    class_tag_map = rb_hash_new();
+    rb_hash_aset(class_tag_map, cASN1EndOfContent, INT2NUM(V_ASN1_EOC));
+    rb_hash_aset(class_tag_map, cASN1Boolean, INT2NUM(V_ASN1_BOOLEAN));
+    rb_hash_aset(class_tag_map, cASN1Integer, INT2NUM(V_ASN1_INTEGER));
+    rb_hash_aset(class_tag_map, cASN1BitString, INT2NUM(V_ASN1_BIT_STRING));
+    rb_hash_aset(class_tag_map, cASN1OctetString, INT2NUM(V_ASN1_OCTET_STRING));
+    rb_hash_aset(class_tag_map, cASN1Null, INT2NUM(V_ASN1_NULL));
+    rb_hash_aset(class_tag_map, cASN1ObjectId, INT2NUM(V_ASN1_OBJECT));
+    rb_hash_aset(class_tag_map, cASN1Enumerated, INT2NUM(V_ASN1_ENUMERATED));
+    rb_hash_aset(class_tag_map, cASN1UTF8String, INT2NUM(V_ASN1_UTF8STRING));
+    rb_hash_aset(class_tag_map, cASN1Sequence, INT2NUM(V_ASN1_SEQUENCE));
+    rb_hash_aset(class_tag_map, cASN1Set, INT2NUM(V_ASN1_SET));
+    rb_hash_aset(class_tag_map, cASN1NumericString, INT2NUM(V_ASN1_NUMERICSTRING));
+    rb_hash_aset(class_tag_map, cASN1PrintableString, INT2NUM(V_ASN1_PRINTABLESTRING));
+    rb_hash_aset(class_tag_map, cASN1T61String, INT2NUM(V_ASN1_T61STRING));
+    rb_hash_aset(class_tag_map, cASN1VideotexString, INT2NUM(V_ASN1_VIDEOTEXSTRING));
+    rb_hash_aset(class_tag_map, cASN1IA5String, INT2NUM(V_ASN1_IA5STRING));
+    rb_hash_aset(class_tag_map, cASN1UTCTime, INT2NUM(V_ASN1_UTCTIME));
+    rb_hash_aset(class_tag_map, cASN1GeneralizedTime, INT2NUM(V_ASN1_GENERALIZEDTIME));
+    rb_hash_aset(class_tag_map, cASN1GraphicString, INT2NUM(V_ASN1_GRAPHICSTRING));
+    rb_hash_aset(class_tag_map, cASN1ISO64String, INT2NUM(V_ASN1_ISO64STRING));
+    rb_hash_aset(class_tag_map, cASN1GeneralString, INT2NUM(V_ASN1_GENERALSTRING));
+    rb_hash_aset(class_tag_map, cASN1UniversalString, INT2NUM(V_ASN1_UNIVERSALSTRING));
+    rb_hash_aset(class_tag_map, cASN1BMPString, INT2NUM(V_ASN1_BMPSTRING));
+    rb_global_variable(&class_tag_map);
+
+    id_each = rb_intern_const("each");
+}
diff --git a/ext/openssl/ossl_asn1.h b/ext/openssl/ossl_asn1.h
new file mode 100644
index 0000000..d6a170c
--- /dev/null
+++ b/ext/openssl/ossl_asn1.h
@@ -0,0 +1,66 @@
+/*
+ * 'OpenSSL for Ruby' team members
+ * Copyright (C) 2003
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#if !defined(_OSSL_ASN1_H_)
+#define _OSSL_ASN1_H_
+
+/*
+ * ASN1_DATE conversions
+ */
+VALUE asn1time_to_time(const ASN1_TIME *);
+#if defined(HAVE_ASN1_TIME_ADJ)
+/* Splits VALUE to seconds and offset days. VALUE is typically a Time or an
+ * Integer. This is used when updating ASN1_*TIME with ASN1_TIME_adj() or
+ * X509_time_adj_ex(). We can't use ASN1_TIME_set() and X509_time_adj() because
+ * they have the Year 2038 issue on sizeof(time_t) == 4 environment */
+void ossl_time_split(VALUE, time_t *, int *);
+#else
+time_t time_to_time_t(VALUE);
+#endif
+
+/*
+ * ASN1_STRING conversions
+ */
+VALUE asn1str_to_str(const ASN1_STRING *);
+
+/*
+ * ASN1_INTEGER conversions
+ */
+VALUE asn1integer_to_num(const ASN1_INTEGER *);
+ASN1_INTEGER *num_to_asn1integer(VALUE, ASN1_INTEGER *);
+
+/*
+ * ASN1 module
+ */
+extern VALUE mASN1;
+extern VALUE eASN1Error;
+
+extern VALUE cASN1Data;
+extern VALUE cASN1Primitive;
+extern VALUE cASN1Constructive;
+
+extern VALUE cASN1Boolean;                           /* BOOLEAN           */
+extern VALUE cASN1Integer, cASN1Enumerated;          /* INTEGER           */
+extern VALUE cASN1BitString;                         /* BIT STRING        */
+extern VALUE cASN1OctetString, cASN1UTF8String;      /* STRINGs           */
+extern VALUE cASN1NumericString, cASN1PrintableString;
+extern VALUE cASN1T61String, cASN1VideotexString;
+extern VALUE cASN1IA5String, cASN1GraphicString;
+extern VALUE cASN1ISO64String, cASN1GeneralString;
+extern VALUE cASN1UniversalString, cASN1BMPString;
+extern VALUE cASN1Null;                              /* NULL              */
+extern VALUE cASN1ObjectId;                          /* OBJECT IDENTIFIER */
+extern VALUE cASN1UTCTime, cASN1GeneralizedTime;     /* TIME              */
+extern VALUE cASN1Sequence, cASN1Set;                /* CONSTRUCTIVE      */
+
+ASN1_TYPE *ossl_asn1_get_asn1type(VALUE);
+
+void Init_ossl_asn1(void);
+
+#endif
diff --git a/ext/openssl/ossl_bio.c b/ext/openssl/ossl_bio.c
new file mode 100644
index 0000000..1609b09
--- /dev/null
+++ b/ext/openssl/ossl_bio.c
@@ -0,0 +1,84 @@
+/*
+ * 'OpenSSL for Ruby' team members
+ * Copyright (C) 2003
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#include "ossl.h"
+
+BIO *
+ossl_obj2bio(VALUE obj)
+{
+    BIO *bio;
+
+    if (RB_TYPE_P(obj, T_FILE)) {
+	rb_io_t *fptr;
+	FILE *fp;
+	int fd;
+
+	GetOpenFile(obj, fptr);
+	rb_io_check_readable(fptr);
+	if ((fd = rb_cloexec_dup(FPTR_TO_FD(fptr))) < 0){
+	    rb_sys_fail(0);
+	}
+        rb_update_max_fd(fd);
+	if (!(fp = fdopen(fd, "r"))){
+	    int e = errno;
+	    close(fd);
+	    rb_syserr_fail(e, 0);
+	}
+	if (!(bio = BIO_new_fp(fp, BIO_CLOSE))){
+	    fclose(fp);
+	    ossl_raise(eOSSLError, NULL);
+	}
+    }
+    else {
+	StringValue(obj);
+	bio = BIO_new_mem_buf(RSTRING_PTR(obj), RSTRING_LENINT(obj));
+	if (!bio) ossl_raise(eOSSLError, NULL);
+    }
+
+    return bio;
+}
+
+BIO *
+ossl_protect_obj2bio(VALUE obj, int *status)
+{
+     BIO *ret = NULL;
+     ret = (BIO*)rb_protect((VALUE (*)(VALUE))ossl_obj2bio, obj, status);
+     return ret;
+}
+
+VALUE
+ossl_membio2str0(BIO *bio)
+{
+    VALUE ret;
+    BUF_MEM *buf;
+
+    BIO_get_mem_ptr(bio, &buf);
+    ret = rb_str_new(buf->data, buf->length);
+
+    return ret;
+}
+
+VALUE
+ossl_protect_membio2str(BIO *bio, int *status)
+{
+    return rb_protect((VALUE (*)(VALUE))ossl_membio2str0, (VALUE)bio, status);
+}
+
+VALUE
+ossl_membio2str(BIO *bio)
+{
+    VALUE ret;
+    int status = 0;
+
+    ret = ossl_protect_membio2str(bio, &status);
+    BIO_free(bio);
+    if(status) rb_jump_tag(status);
+
+    return ret;
+}
diff --git a/ext/openssl/ossl_bio.h b/ext/openssl/ossl_bio.h
new file mode 100644
index 0000000..1705d0a
--- /dev/null
+++ b/ext/openssl/ossl_bio.h
@@ -0,0 +1,19 @@
+/*
+ * 'OpenSSL for Ruby' team members
+ * Copyright (C) 2003
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#if !defined(_OSSL_BIO_H_)
+#define _OSSL_BIO_H_
+
+BIO *ossl_obj2bio(VALUE);
+BIO *ossl_protect_obj2bio(VALUE,int*);
+VALUE ossl_membio2str0(BIO*);
+VALUE ossl_membio2str(BIO*);
+VALUE ossl_protect_membio2str(BIO*,int*);
+
+#endif
diff --git a/ext/openssl/ossl_bn.c b/ext/openssl/ossl_bn.c
new file mode 100644
index 0000000..aa0f2c6
--- /dev/null
+++ b/ext/openssl/ossl_bn.c
@@ -0,0 +1,1160 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2001-2002  Technorama team <oss-ruby at technorama.net>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+/* modified by Michal Rokos <m.rokos at sh.cvut.cz> */
+#include "ossl.h"
+
+#define NewBN(klass) \
+  TypedData_Wrap_Struct((klass), &ossl_bn_type, 0)
+#define SetBN(obj, bn) do { \
+  if (!(bn)) { \
+    ossl_raise(rb_eRuntimeError, "BN wasn't initialized!"); \
+  } \
+  RTYPEDDATA_DATA(obj) = (bn); \
+} while (0)
+
+#define GetBN(obj, bn) do { \
+  TypedData_Get_Struct((obj), BIGNUM, &ossl_bn_type, (bn)); \
+  if (!(bn)) { \
+    ossl_raise(rb_eRuntimeError, "BN wasn't initialized!"); \
+  } \
+} while (0)
+
+#define SafeGetBN(obj, bn) do { \
+  OSSL_Check_Kind((obj), cBN); \
+  GetBN((obj), (bn)); \
+} while (0)
+
+static void
+ossl_bn_free(void *ptr)
+{
+    BN_clear_free(ptr);
+}
+
+static const rb_data_type_t ossl_bn_type = {
+    "OpenSSL/BN",
+    {
+	0, ossl_bn_free,
+    },
+    0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
+};
+
+/*
+ * Classes
+ */
+VALUE cBN;
+
+/* Document-class: OpenSSL::BNError
+ *
+ * Generic Error for all of OpenSSL::BN (big num)
+ */
+VALUE eBNError;
+
+/*
+ * Public
+ */
+VALUE
+ossl_bn_new(const BIGNUM *bn)
+{
+    BIGNUM *newbn;
+    VALUE obj;
+
+    obj = NewBN(cBN);
+    newbn = bn ? BN_dup(bn) : BN_new();
+    if (!newbn) {
+	ossl_raise(eBNError, NULL);
+    }
+    SetBN(obj, newbn);
+
+    return obj;
+}
+
+static BIGNUM *
+integer_to_bnptr(VALUE obj, BIGNUM *orig)
+{
+    BIGNUM *bn;
+
+    if (FIXNUM_P(obj)) {
+	long i;
+	unsigned char bin[sizeof(long)];
+	long n = FIX2LONG(obj);
+	unsigned long un = labs(n);
+
+	for (i = sizeof(long) - 1; 0 <= i; i--) {
+	    bin[i] = un & 0xff;
+	    un >>= 8;
+	}
+
+	bn = BN_bin2bn(bin, sizeof(bin), orig);
+	if (!bn)
+	    ossl_raise(eBNError, "BN_bin2bn");
+	if (n < 0)
+	    BN_set_negative(bn, 1);
+    }
+    else { /* assuming Bignum */
+	size_t len = rb_absint_size(obj, NULL);
+	unsigned char *bin;
+	VALUE buf;
+	int sign;
+
+	if (INT_MAX < len) {
+	    rb_raise(eBNError, "bignum too long");
+	}
+	bin = (unsigned char*)ALLOCV_N(unsigned char, buf, len);
+	sign = rb_integer_pack(obj, bin, len, 1, 0, INTEGER_PACK_BIG_ENDIAN);
+
+	bn = BN_bin2bn(bin, (int)len, orig);
+	ALLOCV_END(buf);
+	if (!bn)
+	    ossl_raise(eBNError, "BN_bin2bn");
+	if (sign < 0)
+	    BN_set_negative(bn, 1);
+    }
+
+    return bn;
+}
+
+static VALUE
+try_convert_to_bn(VALUE obj)
+{
+    BIGNUM *bn;
+    VALUE newobj = Qnil;
+
+    if (rb_obj_is_kind_of(obj, cBN))
+	return obj;
+    if (RB_INTEGER_TYPE_P(obj)) {
+	newobj = NewBN(cBN); /* Handle potential mem leaks */
+	bn = integer_to_bnptr(obj, NULL);
+	SetBN(newobj, bn);
+    }
+
+    return newobj;
+}
+
+BIGNUM *
+ossl_bn_value_ptr(volatile VALUE *ptr)
+{
+    VALUE tmp;
+    BIGNUM *bn;
+
+    tmp = try_convert_to_bn(*ptr);
+    if (NIL_P(tmp))
+	ossl_raise(rb_eTypeError, "Cannot convert into OpenSSL::BN");
+    GetBN(tmp, bn);
+    *ptr = tmp;
+
+    return bn;
+}
+
+/*
+ * Private
+ */
+/*
+ * BN_CTX - is used in more difficult math. ops
+ * (Why just 1? Because Ruby itself isn't thread safe,
+ *  we don't need to care about threads)
+ */
+BN_CTX *ossl_bn_ctx;
+
+static VALUE
+ossl_bn_alloc(VALUE klass)
+{
+    BIGNUM *bn;
+    VALUE obj = NewBN(klass);
+
+    if (!(bn = BN_new())) {
+	ossl_raise(eBNError, NULL);
+    }
+    SetBN(obj, bn);
+
+    return obj;
+}
+
+/* Document-method: OpenSSL::BN.new
+ *
+ * call-seq:
+ *    OpenSSL::BN.new => aBN
+ *    OpenSSL::BN.new(bn) => aBN
+ *    OpenSSL::BN.new(integer) => aBN
+ *    OpenSSL::BN.new(string) => aBN
+ *    OpenSSL::BN.new(string, 0 | 2 | 10 | 16) => aBN
+ *
+ * Construct a new OpenSSL BigNum object.
+ */
+static VALUE
+ossl_bn_initialize(int argc, VALUE *argv, VALUE self)
+{
+    BIGNUM *bn;
+    VALUE str, bs;
+    int base = 10;
+
+    if (rb_scan_args(argc, argv, "11", &str, &bs) == 2) {
+	base = NUM2INT(bs);
+    }
+
+    if (RB_INTEGER_TYPE_P(str)) {
+	GetBN(self, bn);
+	integer_to_bnptr(str, bn);
+
+	return self;
+    }
+
+    if (RTEST(rb_obj_is_kind_of(str, cBN))) {
+	BIGNUM *other;
+
+	GetBN(self, bn);
+	GetBN(str, other); /* Safe - we checked kind_of? above */
+	if (!BN_copy(bn, other)) {
+	    ossl_raise(eBNError, NULL);
+	}
+	return self;
+    }
+
+    GetBN(self, bn);
+    switch (base) {
+    case 0:
+	if (!BN_mpi2bn((unsigned char *)StringValuePtr(str), RSTRING_LENINT(str), bn)) {
+	    ossl_raise(eBNError, NULL);
+	}
+	break;
+    case 2:
+	if (!BN_bin2bn((unsigned char *)StringValuePtr(str), RSTRING_LENINT(str), bn)) {
+	    ossl_raise(eBNError, NULL);
+	}
+	break;
+    case 10:
+	if (!BN_dec2bn(&bn, StringValueCStr(str))) {
+	    ossl_raise(eBNError, NULL);
+	}
+	break;
+    case 16:
+	if (!BN_hex2bn(&bn, StringValueCStr(str))) {
+	    ossl_raise(eBNError, NULL);
+	}
+	break;
+    default:
+	ossl_raise(rb_eArgError, "invalid radix %d", base);
+    }
+    return self;
+}
+
+/*
+ * call-seq:
+ *    bn.to_s => string
+ *    bn.to_s(base) => string
+ *
+ * === Parameters
+ * * +base+ - integer
+ *   Valid values:
+ *   * 0 - MPI
+ *   * 2 - binary
+ *   * 10 - the default
+ *   * 16 - hex
+ */
+static VALUE
+ossl_bn_to_s(int argc, VALUE *argv, VALUE self)
+{
+    BIGNUM *bn;
+    VALUE str, bs;
+    int base = 10, len;
+    char *buf;
+
+    if (rb_scan_args(argc, argv, "01", &bs) == 1) {
+	base = NUM2INT(bs);
+    }
+    GetBN(self, bn);
+    switch (base) {
+    case 0:
+	len = BN_bn2mpi(bn, NULL);
+        str = rb_str_new(0, len);
+	if (BN_bn2mpi(bn, (unsigned char *)RSTRING_PTR(str)) != len)
+	    ossl_raise(eBNError, NULL);
+	break;
+    case 2:
+	len = BN_num_bytes(bn);
+        str = rb_str_new(0, len);
+	if (BN_bn2bin(bn, (unsigned char *)RSTRING_PTR(str)) != len)
+	    ossl_raise(eBNError, NULL);
+	break;
+    case 10:
+	if (!(buf = BN_bn2dec(bn))) ossl_raise(eBNError, NULL);
+	str = ossl_buf2str(buf, rb_long2int(strlen(buf)));
+	break;
+    case 16:
+	if (!(buf = BN_bn2hex(bn))) ossl_raise(eBNError, NULL);
+	str = ossl_buf2str(buf, rb_long2int(strlen(buf)));
+	break;
+    default:
+	ossl_raise(rb_eArgError, "invalid radix %d", base);
+    }
+
+    return str;
+}
+
+/*
+ * call-seq:
+ *    bn.to_i => integer
+ */
+static VALUE
+ossl_bn_to_i(VALUE self)
+{
+    BIGNUM *bn;
+    char *txt;
+    VALUE num;
+
+    GetBN(self, bn);
+
+    if (!(txt = BN_bn2hex(bn))) {
+	ossl_raise(eBNError, NULL);
+    }
+    num = rb_cstr_to_inum(txt, 16, Qtrue);
+    OPENSSL_free(txt);
+
+    return num;
+}
+
+static VALUE
+ossl_bn_to_bn(VALUE self)
+{
+    return self;
+}
+
+static VALUE
+ossl_bn_coerce(VALUE self, VALUE other)
+{
+    switch(TYPE(other)) {
+    case T_STRING:
+	self = ossl_bn_to_s(0, NULL, self);
+	break;
+    case T_FIXNUM:
+    case T_BIGNUM:
+	self = ossl_bn_to_i(self);
+	break;
+    default:
+	if (!RTEST(rb_obj_is_kind_of(other, cBN))) {
+	    ossl_raise(rb_eTypeError, "Don't know how to coerce");
+	}
+    }
+    return rb_assoc_new(other, self);
+}
+
+#define BIGNUM_BOOL1(func)				\
+    static VALUE					\
+    ossl_bn_##func(VALUE self)				\
+    {							\
+	BIGNUM *bn;					\
+	GetBN(self, bn);				\
+	if (BN_##func(bn)) {				\
+	    return Qtrue;				\
+	}						\
+	return Qfalse;					\
+    }
+
+/*
+ * Document-method: OpenSSL::BN#zero?
+ * call-seq:
+ *   bn.zero? => true | false
+ */
+BIGNUM_BOOL1(is_zero)
+
+/*
+ * Document-method: OpenSSL::BN#one?
+ * call-seq:
+ *   bn.one? => true | false
+ */
+BIGNUM_BOOL1(is_one)
+
+/*
+ * Document-method: OpenSSL::BN#odd?
+ * call-seq:
+ *   bn.odd? => true | false
+ */
+BIGNUM_BOOL1(is_odd)
+
+#define BIGNUM_1c(func)					\
+    static VALUE					\
+    ossl_bn_##func(VALUE self)				\
+    {							\
+	BIGNUM *bn, *result;				\
+	VALUE obj;					\
+	GetBN(self, bn);				\
+	obj = NewBN(rb_obj_class(self));		\
+	if (!(result = BN_new())) {			\
+	    ossl_raise(eBNError, NULL);			\
+	}						\
+	if (!BN_##func(result, bn, ossl_bn_ctx)) {	\
+	    BN_free(result);				\
+	    ossl_raise(eBNError, NULL);			\
+	}						\
+	SetBN(obj, result);				\
+	return obj;					\
+    }
+
+/*
+ * Document-method: OpenSSL::BN#sqr
+ * call-seq:
+ *   bn.sqr => aBN
+ */
+BIGNUM_1c(sqr)
+
+#define BIGNUM_2(func)					\
+    static VALUE					\
+    ossl_bn_##func(VALUE self, VALUE other)		\
+    {							\
+	BIGNUM *bn1, *bn2 = GetBNPtr(other), *result;	\
+	VALUE obj;					\
+	GetBN(self, bn1);				\
+	obj = NewBN(rb_obj_class(self));		\
+	if (!(result = BN_new())) {			\
+	    ossl_raise(eBNError, NULL);			\
+	}						\
+	if (!BN_##func(result, bn1, bn2)) {		\
+	    BN_free(result);				\
+	    ossl_raise(eBNError, NULL);			\
+	}						\
+	SetBN(obj, result);				\
+	return obj;					\
+    }
+
+/*
+ * Document-method: OpenSSL::BN#+
+ * call-seq:
+ *   bn + bn2 => aBN
+ */
+BIGNUM_2(add)
+
+/*
+ * Document-method: OpenSSL::BN#-
+ * call-seq:
+ *   bn - bn2 => aBN
+ */
+BIGNUM_2(sub)
+
+#define BIGNUM_2c(func)						\
+    static VALUE						\
+    ossl_bn_##func(VALUE self, VALUE other)			\
+    {								\
+	BIGNUM *bn1, *bn2 = GetBNPtr(other), *result;		\
+	VALUE obj;						\
+	GetBN(self, bn1);					\
+	obj = NewBN(rb_obj_class(self));			\
+	if (!(result = BN_new())) {				\
+	    ossl_raise(eBNError, NULL);				\
+	}							\
+	if (!BN_##func(result, bn1, bn2, ossl_bn_ctx)) {	\
+	    BN_free(result);					\
+	    ossl_raise(eBNError, NULL);				\
+	}							\
+	SetBN(obj, result);					\
+	return obj;						\
+    }
+
+/*
+ * Document-method: OpenSSL::BN#*
+ * call-seq:
+ *   bn * bn2 => aBN
+ */
+BIGNUM_2c(mul)
+
+/*
+ * Document-method: OpenSSL::BN#%
+ * call-seq:
+ *   bn % bn2 => aBN
+ */
+BIGNUM_2c(mod)
+
+/*
+ * Document-method: OpenSSL::BN#**
+ * call-seq:
+ *   bn ** bn2 => aBN
+ */
+BIGNUM_2c(exp)
+
+/*
+ * Document-method: OpenSSL::BN#gcd
+ * call-seq:
+ *   bn.gcd(bn2) => aBN
+ */
+BIGNUM_2c(gcd)
+
+/*
+ * Document-method: OpenSSL::BN#mod_sqr
+ * call-seq:
+ *   bn.mod_sqr(bn2) => aBN
+ */
+BIGNUM_2c(mod_sqr)
+
+/*
+ * Document-method: OpenSSL::BN#mod_inverse
+ * call-seq:
+ *   bn.mod_inverse(bn2) => aBN
+ */
+BIGNUM_2c(mod_inverse)
+
+/*
+ * Document-method: OpenSSL::BN#/
+ * call-seq:
+ *    bn1 / bn2 => [result, remainder]
+ *
+ * Division of OpenSSL::BN instances
+ */
+static VALUE
+ossl_bn_div(VALUE self, VALUE other)
+{
+    BIGNUM *bn1, *bn2 = GetBNPtr(other), *r1, *r2;
+    VALUE klass, obj1, obj2;
+
+    GetBN(self, bn1);
+
+    klass = rb_obj_class(self);
+    obj1 = NewBN(klass);
+    obj2 = NewBN(klass);
+    if (!(r1 = BN_new())) {
+	ossl_raise(eBNError, NULL);
+    }
+    if (!(r2 = BN_new())) {
+	BN_free(r1);
+	ossl_raise(eBNError, NULL);
+    }
+    if (!BN_div(r1, r2, bn1, bn2, ossl_bn_ctx)) {
+	BN_free(r1);
+	BN_free(r2);
+	ossl_raise(eBNError, NULL);
+    }
+    SetBN(obj1, r1);
+    SetBN(obj2, r2);
+
+    return rb_ary_new3(2, obj1, obj2);
+}
+
+#define BIGNUM_3c(func)						\
+    static VALUE						\
+    ossl_bn_##func(VALUE self, VALUE other1, VALUE other2)	\
+    {								\
+	BIGNUM *bn1, *bn2 = GetBNPtr(other1);			\
+	BIGNUM *bn3 = GetBNPtr(other2), *result;		\
+	VALUE obj;						\
+	GetBN(self, bn1);					\
+	obj = NewBN(rb_obj_class(self));			\
+	if (!(result = BN_new())) {				\
+	    ossl_raise(eBNError, NULL);				\
+	}							\
+	if (!BN_##func(result, bn1, bn2, bn3, ossl_bn_ctx)) {	\
+	    BN_free(result);					\
+	    ossl_raise(eBNError, NULL);				\
+	}							\
+	SetBN(obj, result);					\
+	return obj;						\
+    }
+
+/*
+ * Document-method: OpenSSL::BN#mod_add
+ * call-seq:
+ *   bn.mod_add(bn1, bn2) -> aBN
+ */
+BIGNUM_3c(mod_add)
+
+/*
+ * Document-method: OpenSSL::BN#mod_sub
+ * call-seq:
+ *   bn.mod_sub(bn1, bn2) -> aBN
+ */
+BIGNUM_3c(mod_sub)
+
+/*
+ * Document-method: OpenSSL::BN#mod_mul
+ * call-seq:
+ *   bn.mod_mul(bn1, bn2) -> aBN
+ */
+BIGNUM_3c(mod_mul)
+
+/*
+ * Document-method: OpenSSL::BN#mod_exp
+ * call-seq:
+ *   bn.mod_exp(bn1, bn2) -> aBN
+ */
+BIGNUM_3c(mod_exp)
+
+#define BIGNUM_BIT(func)				\
+    static VALUE					\
+    ossl_bn_##func(VALUE self, VALUE bit)		\
+    {							\
+	BIGNUM *bn;					\
+	GetBN(self, bn);				\
+	if (!BN_##func(bn, NUM2INT(bit))) {		\
+	    ossl_raise(eBNError, NULL);			\
+	}						\
+	return self;					\
+    }
+
+/*
+ * Document-method: OpenSSL::BN#set_bit!
+ * call-seq:
+ *   bn.set_bit!(bit) -> self
+ */
+BIGNUM_BIT(set_bit)
+
+/*
+ * Document-method: OpenSSL::BN#clear_bit!
+ * call-seq:
+ *   bn.clear_bit!(bit) -> self
+ */
+BIGNUM_BIT(clear_bit)
+
+/*
+ * Document-method: OpenSSL::BN#mask_bit!
+ * call-seq:
+ *   bn.mask_bit!(bit) -> self
+ */
+BIGNUM_BIT(mask_bits)
+
+/* Document-method: OpenSSL::BN#bit_set?
+ * call-seq:
+ *   bn.bit_set?(bit) => true | false
+ *
+ * Returns boolean of whether +bit+ is set.
+ * Bitwise operations for openssl BIGNUMs.
+ */
+static VALUE
+ossl_bn_is_bit_set(VALUE self, VALUE bit)
+{
+    int b;
+    BIGNUM *bn;
+
+    b = NUM2INT(bit);
+    GetBN(self, bn);
+    if (BN_is_bit_set(bn, b)) {
+	return Qtrue;
+    }
+    return Qfalse;
+}
+
+#define BIGNUM_SHIFT(func)				\
+    static VALUE					\
+    ossl_bn_##func(VALUE self, VALUE bits)		\
+    {							\
+	BIGNUM *bn, *result;				\
+	int b;						\
+	VALUE obj;					\
+	b = NUM2INT(bits);				\
+	GetBN(self, bn);				\
+	obj = NewBN(rb_obj_class(self));		\
+	if (!(result = BN_new())) {			\
+		ossl_raise(eBNError, NULL);		\
+	}						\
+	if (!BN_##func(result, bn, b)) {		\
+		BN_free(result);			\
+		ossl_raise(eBNError, NULL);		\
+	}						\
+	SetBN(obj, result);				\
+	return obj;					\
+    }
+
+/*
+ * Document-method: OpenSSL::BN#<<
+ * call-seq:
+ *   bn << bits -> aBN
+ */
+BIGNUM_SHIFT(lshift)
+
+/*
+ * Document-method: OpenSSL::BN#>>
+ * call-seq:
+ *   bn >> bits -> aBN
+ */
+BIGNUM_SHIFT(rshift)
+
+#define BIGNUM_SELF_SHIFT(func)				\
+    static VALUE					\
+    ossl_bn_self_##func(VALUE self, VALUE bits)		\
+    {							\
+	BIGNUM *bn;					\
+	int b;						\
+	b = NUM2INT(bits);				\
+	GetBN(self, bn);				\
+	if (!BN_##func(bn, bn, b))			\
+		ossl_raise(eBNError, NULL);		\
+	return self;					\
+    }
+
+/*
+ * Document-method: OpenSSL::BN#lshift!
+ * call-seq:
+ *   bn.lshift!(bits) -> self
+ */
+BIGNUM_SELF_SHIFT(lshift)
+
+/*
+ * Document-method: OpenSSL::BN#rshift!
+ * call-seq:
+ *   bn.rshift!(bits) -> self
+ */
+BIGNUM_SELF_SHIFT(rshift)
+
+#define BIGNUM_RAND(func)					\
+    static VALUE						\
+    ossl_bn_s_##func(int argc, VALUE *argv, VALUE klass)	\
+    {								\
+	BIGNUM *result;						\
+	int bottom = 0, top = 0, b;				\
+	VALUE bits, fill, odd, obj;				\
+								\
+	switch (rb_scan_args(argc, argv, "12", &bits, &fill, &odd)) {	\
+	case 3:							\
+	    bottom = (odd == Qtrue) ? 1 : 0;			\
+	    /* FALLTHROUGH */					\
+	case 2:							\
+	    top = NUM2INT(fill);				\
+	}							\
+	b = NUM2INT(bits);					\
+	obj = NewBN(klass);					\
+	if (!(result = BN_new())) {				\
+	    ossl_raise(eBNError, NULL);				\
+	}							\
+	if (!BN_##func(result, b, top, bottom)) {		\
+	    BN_free(result);					\
+	    ossl_raise(eBNError, NULL);				\
+	}							\
+	SetBN(obj, result);					\
+	return obj;						\
+    }
+
+/*
+ * Document-method: OpenSSL::BN.rand
+ *   BN.rand(bits [, fill [, odd]]) -> aBN
+ */
+BIGNUM_RAND(rand)
+
+/*
+ * Document-method: OpenSSL::BN.pseudo_rand
+ *   BN.pseudo_rand(bits [, fill [, odd]]) -> aBN
+ */
+BIGNUM_RAND(pseudo_rand)
+
+#define BIGNUM_RAND_RANGE(func)					\
+    static VALUE						\
+    ossl_bn_s_##func##_range(VALUE klass, VALUE range)		\
+    {								\
+	BIGNUM *bn = GetBNPtr(range), *result;			\
+	VALUE obj = NewBN(klass);				\
+	if (!(result = BN_new())) {				\
+	    ossl_raise(eBNError, NULL);				\
+	}							\
+	if (!BN_##func##_range(result, bn)) {			\
+	    BN_free(result);					\
+	    ossl_raise(eBNError, NULL);				\
+	}							\
+	SetBN(obj, result);					\
+	return obj;						\
+    }
+
+/*
+ * Document-method: OpenSSL::BN.rand_range
+ * call-seq:
+ *   BN.rand_range(range) -> aBN
+ *
+ */
+BIGNUM_RAND_RANGE(rand)
+
+/*
+ * Document-method: OpenSSL::BN.pseudo_rand_range
+ * call-seq:
+ *   BN.pseudo_rand_range(range) -> aBN
+ *
+ */
+BIGNUM_RAND_RANGE(pseudo_rand)
+
+/*
+ * call-seq:
+ *    BN.generate_prime(bits, [, safe [, add [, rem]]]) => bn
+ *
+ * Generates a random prime number of bit length +bits+. If +safe+ is true,
+ * generates a safe prime. If +add+ is specified, generates a prime that
+ * fulfills condition <tt>p % add = rem</tt>.
+ *
+ * === Parameters
+ * * +bits+ - integer
+ * * +safe+ - boolean
+ * * +add+ - BN
+ * * +rem+ - BN
+ */
+static VALUE
+ossl_bn_s_generate_prime(int argc, VALUE *argv, VALUE klass)
+{
+    BIGNUM *add = NULL, *rem = NULL, *result;
+    int safe = 1, num;
+    VALUE vnum, vsafe, vadd, vrem, obj;
+
+    rb_scan_args(argc, argv, "13", &vnum, &vsafe, &vadd, &vrem);
+
+    num = NUM2INT(vnum);
+
+    if (vsafe == Qfalse) {
+	safe = 0;
+    }
+    if (!NIL_P(vadd)) {
+	add = GetBNPtr(vadd);
+	rem = NIL_P(vrem) ? NULL : GetBNPtr(vrem);
+    }
+    obj = NewBN(klass);
+    if (!(result = BN_new())) {
+	ossl_raise(eBNError, NULL);
+    }
+    if (!BN_generate_prime_ex(result, num, safe, add, rem, NULL)) {
+	BN_free(result);
+	ossl_raise(eBNError, NULL);
+    }
+    SetBN(obj, result);
+
+    return obj;
+}
+
+#define BIGNUM_NUM(func)			\
+    static VALUE 				\
+    ossl_bn_##func(VALUE self)			\
+    {						\
+	BIGNUM *bn;				\
+	GetBN(self, bn);			\
+	return INT2NUM(BN_##func(bn));		\
+    }
+
+/*
+ * Document-method: OpenSSL::BN#num_bytes
+ * call-seq:
+ *   bn.num_bytes => integer
+ */
+BIGNUM_NUM(num_bytes)
+
+/*
+ * Document-method: OpenSSL::BN#num_bits
+ * call-seq:
+ *   bn.num_bits => integer
+ */
+BIGNUM_NUM(num_bits)
+
+static VALUE
+ossl_bn_copy(VALUE self, VALUE other)
+{
+    BIGNUM *bn1, *bn2;
+
+    rb_check_frozen(self);
+
+    if (self == other) return self;
+
+    GetBN(self, bn1);
+    bn2 = GetBNPtr(other);
+
+    if (!BN_copy(bn1, bn2)) {
+	ossl_raise(eBNError, NULL);
+    }
+    return self;
+}
+
+#define BIGNUM_CMP(func)				\
+    static VALUE					\
+    ossl_bn_##func(VALUE self, VALUE other)		\
+    {							\
+	BIGNUM *bn1, *bn2 = GetBNPtr(other);		\
+	GetBN(self, bn1);				\
+	return INT2NUM(BN_##func(bn1, bn2));		\
+    }
+
+/*
+ * Document-method: OpenSSL::BN#cmp
+ * call-seq:
+ *   bn.cmp(bn2) => integer
+ */
+/*
+ * Document-method: OpenSSL::BN#<=>
+ * call-seq:
+ *   bn <=> bn2 => integer
+ */
+BIGNUM_CMP(cmp)
+
+/*
+ * Document-method: OpenSSL::BN#ucmp
+ * call-seq:
+ *   bn.ucmp(bn2) => integer
+ */
+BIGNUM_CMP(ucmp)
+
+/*
+ *  call-seq:
+ *     bn == obj => true or false
+ *
+ *  Returns +true+ only if +obj+ has the same value as +bn+. Contrast this
+ *  with OpenSSL::BN#eql?, which requires obj to be OpenSSL::BN.
+ */
+static VALUE
+ossl_bn_eq(VALUE self, VALUE other)
+{
+    BIGNUM *bn1, *bn2;
+
+    GetBN(self, bn1);
+    other = try_convert_to_bn(other);
+    if (NIL_P(other))
+	return Qfalse;
+    GetBN(other, bn2);
+
+    if (!BN_cmp(bn1, bn2)) {
+	return Qtrue;
+    }
+    return Qfalse;
+}
+
+/*
+ *  call-seq:
+ *     bn.eql?(obj) => true or false
+ *
+ *  Returns <code>true</code> only if <i>obj</i> is a
+ *  <code>OpenSSL::BN</code> with the same value as <i>big</i>. Contrast this
+ *  with OpenSSL::BN#==, which performs type conversions.
+ */
+static VALUE
+ossl_bn_eql(VALUE self, VALUE other)
+{
+    BIGNUM *bn1, *bn2;
+
+    if (!rb_obj_is_kind_of(other, cBN))
+	return Qfalse;
+    GetBN(self, bn1);
+    GetBN(other, bn2);
+
+    return BN_cmp(bn1, bn2) ? Qfalse : Qtrue;
+}
+
+/*
+ *  call-seq:
+ *     bn.hash => Integer
+ *
+ *  Returns a hash code for this object.
+ *
+ *  See also Object#hash.
+ */
+static VALUE
+ossl_bn_hash(VALUE self)
+{
+    BIGNUM *bn;
+    VALUE hash;
+    unsigned char *buf;
+    int len;
+
+    GetBN(self, bn);
+    len = BN_num_bytes(bn);
+    buf = xmalloc(len);
+    if (BN_bn2bin(bn, buf) != len) {
+	xfree(buf);
+	ossl_raise(eBNError, NULL);
+    }
+
+    hash = INT2FIX(rb_memhash(buf, len));
+    xfree(buf);
+
+    return hash;
+}
+
+/*
+ * call-seq:
+ *    bn.prime? => true | false
+ *    bn.prime?(checks) => true | false
+ *
+ * Performs a Miller-Rabin probabilistic primality test with +checks+
+ * iterations. If +nchecks+ is not specified, a number of iterations is used
+ * that yields a false positive rate of at most 2^-80 for random input.
+ *
+ * === Parameters
+ * * +checks+ - integer
+ */
+static VALUE
+ossl_bn_is_prime(int argc, VALUE *argv, VALUE self)
+{
+    BIGNUM *bn;
+    VALUE vchecks;
+    int checks = BN_prime_checks;
+
+    if (rb_scan_args(argc, argv, "01", &vchecks) == 1) {
+	checks = NUM2INT(vchecks);
+    }
+    GetBN(self, bn);
+    switch (BN_is_prime_ex(bn, checks, ossl_bn_ctx, NULL)) {
+    case 1:
+	return Qtrue;
+    case 0:
+	return Qfalse;
+    default:
+	ossl_raise(eBNError, NULL);
+    }
+    /* not reachable */
+    return Qnil;
+}
+
+/*
+ * call-seq:
+ *    bn.prime_fasttest? => true | false
+ *    bn.prime_fasttest?(checks) => true | false
+ *    bn.prime_fasttest?(checks, trial_div) => true | false
+ *
+ * Performs a Miller-Rabin primality test. This is same as #prime? except this
+ * first attempts trial divisions with some small primes.
+ *
+ * === Parameters
+ * * +checks+ - integer
+ * * +trial_div+ - boolean
+ */
+static VALUE
+ossl_bn_is_prime_fasttest(int argc, VALUE *argv, VALUE self)
+{
+    BIGNUM *bn;
+    VALUE vchecks, vtrivdiv;
+    int checks = BN_prime_checks, do_trial_division = 1;
+
+    rb_scan_args(argc, argv, "02", &vchecks, &vtrivdiv);
+
+    if (!NIL_P(vchecks)) {
+	checks = NUM2INT(vchecks);
+    }
+    GetBN(self, bn);
+    /* handle true/false */
+    if (vtrivdiv == Qfalse) {
+	do_trial_division = 0;
+    }
+    switch (BN_is_prime_fasttest_ex(bn, checks, ossl_bn_ctx, do_trial_division, NULL)) {
+    case 1:
+	return Qtrue;
+    case 0:
+	return Qfalse;
+    default:
+	ossl_raise(eBNError, NULL);
+    }
+    /* not reachable */
+    return Qnil;
+}
+
+/*
+ * INIT
+ * (NOTE: ordering of methods is the same as in 'man bn')
+ */
+void
+Init_ossl_bn(void)
+{
+#if 0
+    mOSSL = rb_define_module("OpenSSL");
+    eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError);
+#endif
+
+    if (!(ossl_bn_ctx = BN_CTX_new())) {
+	ossl_raise(rb_eRuntimeError, "Cannot init BN_CTX");
+    }
+
+    eBNError = rb_define_class_under(mOSSL, "BNError", eOSSLError);
+
+    cBN = rb_define_class_under(mOSSL, "BN", rb_cObject);
+
+    rb_define_alloc_func(cBN, ossl_bn_alloc);
+    rb_define_method(cBN, "initialize", ossl_bn_initialize, -1);
+
+    rb_define_copy_func(cBN, ossl_bn_copy);
+    rb_define_method(cBN, "copy", ossl_bn_copy, 1);
+
+    /* swap (=coerce?) */
+
+    rb_define_method(cBN, "num_bytes", ossl_bn_num_bytes, 0);
+    rb_define_method(cBN, "num_bits", ossl_bn_num_bits, 0);
+    /* num_bits_word */
+
+    rb_define_method(cBN, "+", ossl_bn_add, 1);
+    rb_define_method(cBN, "-", ossl_bn_sub, 1);
+    rb_define_method(cBN, "*", ossl_bn_mul, 1);
+    rb_define_method(cBN, "sqr", ossl_bn_sqr, 0);
+    rb_define_method(cBN, "/", ossl_bn_div, 1);
+    rb_define_method(cBN, "%", ossl_bn_mod, 1);
+    /* nnmod */
+
+    rb_define_method(cBN, "mod_add", ossl_bn_mod_add, 2);
+    rb_define_method(cBN, "mod_sub", ossl_bn_mod_sub, 2);
+    rb_define_method(cBN, "mod_mul", ossl_bn_mod_mul, 2);
+    rb_define_method(cBN, "mod_sqr", ossl_bn_mod_sqr, 1);
+    rb_define_method(cBN, "**", ossl_bn_exp, 1);
+    rb_define_method(cBN, "mod_exp", ossl_bn_mod_exp, 2);
+    rb_define_method(cBN, "gcd", ossl_bn_gcd, 1);
+
+    /* add_word
+     * sub_word
+     * mul_word
+     * div_word
+     * mod_word */
+
+    rb_define_method(cBN, "cmp", ossl_bn_cmp, 1);
+    rb_define_alias(cBN, "<=>", "cmp");
+    rb_define_method(cBN, "ucmp", ossl_bn_ucmp, 1);
+    rb_define_method(cBN, "eql?", ossl_bn_eql, 1);
+    rb_define_method(cBN, "hash", ossl_bn_hash, 0);
+    rb_define_method(cBN, "==", ossl_bn_eq, 1);
+    rb_define_alias(cBN, "===", "==");
+    rb_define_method(cBN, "zero?", ossl_bn_is_zero, 0);
+    rb_define_method(cBN, "one?", ossl_bn_is_one, 0);
+    /* is_word */
+    rb_define_method(cBN, "odd?", ossl_bn_is_odd, 0);
+
+    /* zero
+     * one
+     * value_one - DON'T IMPL.
+     * set_word
+     * get_word */
+
+    rb_define_singleton_method(cBN, "rand", ossl_bn_s_rand, -1);
+    rb_define_singleton_method(cBN, "pseudo_rand", ossl_bn_s_pseudo_rand, -1);
+    rb_define_singleton_method(cBN, "rand_range", ossl_bn_s_rand_range, 1);
+    rb_define_singleton_method(cBN, "pseudo_rand_range", ossl_bn_s_pseudo_rand_range, 1);
+
+    rb_define_singleton_method(cBN, "generate_prime", ossl_bn_s_generate_prime, -1);
+    rb_define_method(cBN, "prime?", ossl_bn_is_prime, -1);
+    rb_define_method(cBN, "prime_fasttest?", ossl_bn_is_prime_fasttest, -1);
+
+    rb_define_method(cBN, "set_bit!", ossl_bn_set_bit, 1);
+    rb_define_method(cBN, "clear_bit!", ossl_bn_clear_bit, 1);
+    rb_define_method(cBN, "bit_set?", ossl_bn_is_bit_set, 1);
+    rb_define_method(cBN, "mask_bits!", ossl_bn_mask_bits, 1);
+    rb_define_method(cBN, "<<", ossl_bn_lshift, 1);
+    rb_define_method(cBN, ">>", ossl_bn_rshift, 1);
+    rb_define_method(cBN, "lshift!", ossl_bn_self_lshift, 1);
+    rb_define_method(cBN, "rshift!", ossl_bn_self_rshift, 1);
+    /* lshift1 - DON'T IMPL. */
+    /* rshift1 - DON'T IMPL. */
+
+    /*
+     * bn2bin
+     * bin2bn
+     * bn2hex
+     * bn2dec
+     * hex2bn
+     * dec2bn - all these are implemented in ossl_bn_initialize, and ossl_bn_to_s
+     * print - NOT IMPL.
+     * print_fp - NOT IMPL.
+     * bn2mpi
+     * mpi2bn
+     */
+    rb_define_method(cBN, "to_s", ossl_bn_to_s, -1);
+    rb_define_method(cBN, "to_i", ossl_bn_to_i, 0);
+    rb_define_alias(cBN, "to_int", "to_i");
+    rb_define_method(cBN, "to_bn", ossl_bn_to_bn, 0);
+    rb_define_method(cBN, "coerce", ossl_bn_coerce, 1);
+
+    /*
+     * TODO:
+     * But how to: from_bin, from_mpi? PACK?
+     * to_bin
+     * to_mpi
+     */
+
+    rb_define_method(cBN, "mod_inverse", ossl_bn_mod_inverse, 1);
+
+    /* RECiProcal
+     * MONTgomery */
+}
diff --git a/ext/openssl/ossl_bn.h b/ext/openssl/ossl_bn.h
new file mode 100644
index 0000000..a19ba19
--- /dev/null
+++ b/ext/openssl/ossl_bn.h
@@ -0,0 +1,25 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2001-2002  Michal Rokos <m.rokos at sh.cvut.cz>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#if !defined(_OSSL_BN_H_)
+#define _OSSL_BN_H_
+
+extern VALUE cBN;
+extern VALUE eBNError;
+
+extern BN_CTX *ossl_bn_ctx;
+
+#define GetBNPtr(obj) ossl_bn_value_ptr(&(obj))
+
+VALUE ossl_bn_new(const BIGNUM *);
+BIGNUM *ossl_bn_value_ptr(volatile VALUE *);
+void Init_ossl_bn(void);
+
+
+#endif /* _OSS_BN_H_ */
diff --git a/ext/openssl/ossl_cipher.c b/ext/openssl/ossl_cipher.c
new file mode 100644
index 0000000..c2f0927
--- /dev/null
+++ b/ext/openssl/ossl_cipher.c
@@ -0,0 +1,1084 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2001-2002  Michal Rokos <m.rokos at sh.cvut.cz>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#include "ossl.h"
+
+#define NewCipher(klass) \
+    TypedData_Wrap_Struct((klass), &ossl_cipher_type, 0)
+#define AllocCipher(obj, ctx) do { \
+    (ctx) = EVP_CIPHER_CTX_new(); \
+    if (!(ctx)) \
+	ossl_raise(rb_eRuntimeError, NULL); \
+    RTYPEDDATA_DATA(obj) = (ctx); \
+} while (0)
+#define GetCipherInit(obj, ctx) do { \
+    TypedData_Get_Struct((obj), EVP_CIPHER_CTX, &ossl_cipher_type, (ctx)); \
+} while (0)
+#define GetCipher(obj, ctx) do { \
+    GetCipherInit((obj), (ctx)); \
+    if (!(ctx)) { \
+	ossl_raise(rb_eRuntimeError, "Cipher not initialized!"); \
+    } \
+} while (0)
+#define SafeGetCipher(obj, ctx) do { \
+    OSSL_Check_Kind((obj), cCipher); \
+    GetCipher((obj), (ctx)); \
+} while (0)
+
+/*
+ * Classes
+ */
+VALUE cCipher;
+VALUE eCipherError;
+static ID id_auth_tag_len, id_key_set;
+
+static VALUE ossl_cipher_alloc(VALUE klass);
+static void ossl_cipher_free(void *ptr);
+
+static const rb_data_type_t ossl_cipher_type = {
+    "OpenSSL/Cipher",
+    {
+	0, ossl_cipher_free,
+    },
+    0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
+};
+
+/*
+ * PUBLIC
+ */
+const EVP_CIPHER *
+GetCipherPtr(VALUE obj)
+{
+    if (rb_obj_is_kind_of(obj, cCipher)) {
+	EVP_CIPHER_CTX *ctx;
+
+	GetCipher(obj, ctx);
+
+	return EVP_CIPHER_CTX_cipher(ctx);
+    }
+    else {
+	const EVP_CIPHER *cipher;
+
+	StringValueCStr(obj);
+	cipher = EVP_get_cipherbyname(RSTRING_PTR(obj));
+	if (!cipher)
+	    ossl_raise(rb_eArgError,
+		       "unsupported cipher algorithm: %"PRIsVALUE, obj);
+
+	return cipher;
+    }
+}
+
+VALUE
+ossl_cipher_new(const EVP_CIPHER *cipher)
+{
+    VALUE ret;
+    EVP_CIPHER_CTX *ctx;
+
+    ret = ossl_cipher_alloc(cCipher);
+    AllocCipher(ret, ctx);
+    if (EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, -1) != 1)
+	ossl_raise(eCipherError, NULL);
+
+    return ret;
+}
+
+/*
+ * PRIVATE
+ */
+static void
+ossl_cipher_free(void *ptr)
+{
+    EVP_CIPHER_CTX_free(ptr);
+}
+
+static VALUE
+ossl_cipher_alloc(VALUE klass)
+{
+    return NewCipher(klass);
+}
+
+/*
+ *  call-seq:
+ *     Cipher.new(string) -> cipher
+ *
+ *  The string must contain a valid cipher name like "AES-128-CBC" or "3DES".
+ *
+ *  A list of cipher names is available by calling OpenSSL::Cipher.ciphers.
+ */
+static VALUE
+ossl_cipher_initialize(VALUE self, VALUE str)
+{
+    EVP_CIPHER_CTX *ctx;
+    const EVP_CIPHER *cipher;
+    char *name;
+
+    name = StringValueCStr(str);
+    GetCipherInit(self, ctx);
+    if (ctx) {
+	ossl_raise(rb_eRuntimeError, "Cipher already initialized!");
+    }
+    AllocCipher(self, ctx);
+    if (!(cipher = EVP_get_cipherbyname(name))) {
+	ossl_raise(rb_eRuntimeError, "unsupported cipher algorithm (%"PRIsVALUE")", str);
+    }
+    if (EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, -1) != 1)
+	ossl_raise(eCipherError, NULL);
+
+    return self;
+}
+
+static VALUE
+ossl_cipher_copy(VALUE self, VALUE other)
+{
+    EVP_CIPHER_CTX *ctx1, *ctx2;
+
+    rb_check_frozen(self);
+    if (self == other) return self;
+
+    GetCipherInit(self, ctx1);
+    if (!ctx1) {
+	AllocCipher(self, ctx1);
+    }
+    SafeGetCipher(other, ctx2);
+    if (EVP_CIPHER_CTX_copy(ctx1, ctx2) != 1)
+	ossl_raise(eCipherError, NULL);
+
+    return self;
+}
+
+static void*
+add_cipher_name_to_ary(const OBJ_NAME *name, VALUE ary)
+{
+    rb_ary_push(ary, rb_str_new2(name->name));
+    return NULL;
+}
+
+/*
+ *  call-seq:
+ *     OpenSSL::Cipher.ciphers -> array[string...]
+ *
+ *  Returns the names of all available ciphers in an array.
+ */
+static VALUE
+ossl_s_ciphers(VALUE self)
+{
+    VALUE ary;
+
+    ary = rb_ary_new();
+    OBJ_NAME_do_all_sorted(OBJ_NAME_TYPE_CIPHER_METH,
+                    (void(*)(const OBJ_NAME*,void*))add_cipher_name_to_ary,
+                    (void*)ary);
+
+    return ary;
+}
+
+/*
+ *  call-seq:
+ *     cipher.reset -> self
+ *
+ *  Fully resets the internal state of the Cipher. By using this, the same
+ *  Cipher instance may be used several times for encryption or decryption tasks.
+ *
+ *  Internally calls EVP_CipherInit_ex(ctx, NULL, NULL, NULL, NULL, -1).
+ */
+static VALUE
+ossl_cipher_reset(VALUE self)
+{
+    EVP_CIPHER_CTX *ctx;
+
+    GetCipher(self, ctx);
+    if (EVP_CipherInit_ex(ctx, NULL, NULL, NULL, NULL, -1) != 1)
+	ossl_raise(eCipherError, NULL);
+
+    return self;
+}
+
+static VALUE
+ossl_cipher_init(int argc, VALUE *argv, VALUE self, int mode)
+{
+    EVP_CIPHER_CTX *ctx;
+    unsigned char key[EVP_MAX_KEY_LENGTH], *p_key = NULL;
+    unsigned char iv[EVP_MAX_IV_LENGTH], *p_iv = NULL;
+    VALUE pass, init_v;
+
+    if(rb_scan_args(argc, argv, "02", &pass, &init_v) > 0){
+	/*
+	 * oops. this code mistakes salt for IV.
+	 * We deprecated the arguments for this method, but we decided
+	 * keeping this behaviour for backward compatibility.
+	 */
+	VALUE cname  = rb_class_path(rb_obj_class(self));
+	rb_warn("arguments for %"PRIsVALUE"#encrypt and %"PRIsVALUE"#decrypt were deprecated; "
+                "use %"PRIsVALUE"#pkcs5_keyivgen to derive key and IV",
+                cname, cname, cname);
+	StringValue(pass);
+	GetCipher(self, ctx);
+	if (NIL_P(init_v)) memcpy(iv, "OpenSSL for Ruby rulez!", sizeof(iv));
+	else{
+	    StringValue(init_v);
+	    if (EVP_MAX_IV_LENGTH > RSTRING_LEN(init_v)) {
+		memset(iv, 0, EVP_MAX_IV_LENGTH);
+		memcpy(iv, RSTRING_PTR(init_v), RSTRING_LEN(init_v));
+	    }
+	    else memcpy(iv, RSTRING_PTR(init_v), sizeof(iv));
+	}
+	EVP_BytesToKey(EVP_CIPHER_CTX_cipher(ctx), EVP_md5(), iv,
+		       (unsigned char *)RSTRING_PTR(pass), RSTRING_LENINT(pass), 1, key, NULL);
+	p_key = key;
+	p_iv = iv;
+    }
+    else {
+	GetCipher(self, ctx);
+    }
+    if (EVP_CipherInit_ex(ctx, NULL, NULL, p_key, p_iv, mode) != 1) {
+	ossl_raise(eCipherError, NULL);
+    }
+
+    if (p_key)
+	rb_ivar_set(self, id_key_set, Qtrue);
+
+    return self;
+}
+
+/*
+ *  call-seq:
+ *     cipher.encrypt -> self
+ *
+ *  Initializes the Cipher for encryption.
+ *
+ *  Make sure to call Cipher#encrypt or Cipher#decrypt before using any of the
+ *  following methods:
+ *  * [#key=, #iv=, #random_key, #random_iv, #pkcs5_keyivgen]
+ *
+ *  Internally calls EVP_CipherInit_ex(ctx, NULL, NULL, NULL, NULL, 1).
+ */
+static VALUE
+ossl_cipher_encrypt(int argc, VALUE *argv, VALUE self)
+{
+    return ossl_cipher_init(argc, argv, self, 1);
+}
+
+/*
+ *  call-seq:
+ *     cipher.decrypt -> self
+ *
+ *  Initializes the Cipher for decryption.
+ *
+ *  Make sure to call Cipher#encrypt or Cipher#decrypt before using any of the
+ *  following methods:
+ *  * [#key=, #iv=, #random_key, #random_iv, #pkcs5_keyivgen]
+ *
+ *  Internally calls EVP_CipherInit_ex(ctx, NULL, NULL, NULL, NULL, 0).
+ */
+static VALUE
+ossl_cipher_decrypt(int argc, VALUE *argv, VALUE self)
+{
+    return ossl_cipher_init(argc, argv, self, 0);
+}
+
+/*
+ *  call-seq:
+ *     cipher.pkcs5_keyivgen(pass, salt = nil, iterations = 2048, digest = "MD5") -> nil
+ *
+ *  Generates and sets the key/IV based on a password.
+ *
+ *  *WARNING*: This method is only PKCS5 v1.5 compliant when using RC2, RC4-40,
+ *  or DES with MD5 or SHA1. Using anything else (like AES) will generate the
+ *  key/iv using an OpenSSL specific method. This method is deprecated and
+ *  should no longer be used. Use a PKCS5 v2 key generation method from
+ *  OpenSSL::PKCS5 instead.
+ *
+ *  === Parameters
+ *  * +salt+ must be an 8 byte string if provided.
+ *  * +iterations+ is an integer with a default of 2048.
+ *  * +digest+ is a Digest object that defaults to 'MD5'
+ *
+ *  A minimum of 1000 iterations is recommended.
+ *
+ */
+static VALUE
+ossl_cipher_pkcs5_keyivgen(int argc, VALUE *argv, VALUE self)
+{
+    EVP_CIPHER_CTX *ctx;
+    const EVP_MD *digest;
+    VALUE vpass, vsalt, viter, vdigest;
+    unsigned char key[EVP_MAX_KEY_LENGTH], iv[EVP_MAX_IV_LENGTH], *salt = NULL;
+    int iter;
+
+    rb_scan_args(argc, argv, "13", &vpass, &vsalt, &viter, &vdigest);
+    StringValue(vpass);
+    if(!NIL_P(vsalt)){
+	StringValue(vsalt);
+	if(RSTRING_LEN(vsalt) != PKCS5_SALT_LEN)
+	    ossl_raise(eCipherError, "salt must be an 8-octet string");
+	salt = (unsigned char *)RSTRING_PTR(vsalt);
+    }
+    iter = NIL_P(viter) ? 2048 : NUM2INT(viter);
+    digest = NIL_P(vdigest) ? EVP_md5() : GetDigestPtr(vdigest);
+    GetCipher(self, ctx);
+    EVP_BytesToKey(EVP_CIPHER_CTX_cipher(ctx), digest, salt,
+		   (unsigned char *)RSTRING_PTR(vpass), RSTRING_LENINT(vpass), iter, key, iv);
+    if (EVP_CipherInit_ex(ctx, NULL, NULL, key, iv, -1) != 1)
+	ossl_raise(eCipherError, NULL);
+    OPENSSL_cleanse(key, sizeof key);
+    OPENSSL_cleanse(iv, sizeof iv);
+
+    rb_ivar_set(self, id_key_set, Qtrue);
+
+    return Qnil;
+}
+
+static int
+ossl_cipher_update_long(EVP_CIPHER_CTX *ctx, unsigned char *out, long *out_len_ptr,
+			const unsigned char *in, long in_len)
+{
+    int out_part_len;
+    int limit = INT_MAX / 2 + 1;
+    long out_len = 0;
+
+    do {
+	int in_part_len = in_len > limit ? limit : (int)in_len;
+
+	if (!EVP_CipherUpdate(ctx, out ? (out + out_len) : 0,
+			      &out_part_len, in, in_part_len))
+	    return 0;
+
+	out_len += out_part_len;
+	in += in_part_len;
+    } while ((in_len -= limit) > 0);
+
+    if (out_len_ptr)
+	*out_len_ptr = out_len;
+
+    return 1;
+}
+
+/*
+ *  call-seq:
+ *     cipher.update(data [, buffer]) -> string or buffer
+ *
+ *  Encrypts data in a streaming fashion. Hand consecutive blocks of data
+ *  to the +update+ method in order to encrypt it. Returns the encrypted
+ *  data chunk. When done, the output of Cipher#final should be additionally
+ *  added to the result.
+ *
+ *  If +buffer+ is given, the encryption/decryption result will be written to
+ *  it. +buffer+ will be resized automatically.
+ */
+static VALUE
+ossl_cipher_update(int argc, VALUE *argv, VALUE self)
+{
+    EVP_CIPHER_CTX *ctx;
+    unsigned char *in;
+    long in_len, out_len;
+    VALUE data, str;
+
+    rb_scan_args(argc, argv, "11", &data, &str);
+
+    if (!RTEST(rb_attr_get(self, id_key_set)))
+	ossl_raise(eCipherError, "key not set");
+
+    StringValue(data);
+    in = (unsigned char *)RSTRING_PTR(data);
+    if ((in_len = RSTRING_LEN(data)) == 0)
+        ossl_raise(rb_eArgError, "data must not be empty");
+    GetCipher(self, ctx);
+    out_len = in_len+EVP_CIPHER_CTX_block_size(ctx);
+    if (out_len <= 0) {
+	ossl_raise(rb_eRangeError,
+		   "data too big to make output buffer: %ld bytes", in_len);
+    }
+
+    if (NIL_P(str)) {
+        str = rb_str_new(0, out_len);
+    } else {
+        StringValue(str);
+        rb_str_resize(str, out_len);
+    }
+
+    if (!ossl_cipher_update_long(ctx, (unsigned char *)RSTRING_PTR(str), &out_len, in, in_len))
+	ossl_raise(eCipherError, NULL);
+    assert(out_len < RSTRING_LEN(str));
+    rb_str_set_len(str, out_len);
+
+    return str;
+}
+
+/*
+ *  call-seq:
+ *     cipher.final -> string
+ *
+ *  Returns the remaining data held in the cipher object. Further calls to
+ *  Cipher#update or Cipher#final will return garbage. This call should always
+ *  be made as the last call of an encryption or decryption operation, after
+ *  having fed the entire plaintext or ciphertext to the Cipher instance.
+ *
+ *  If an authenticated cipher was used, a CipherError is raised if the tag
+ *  could not be authenticated successfully. Only call this method after
+ *  setting the authentication tag and passing the entire contents of the
+ *  ciphertext into the cipher.
+ */
+static VALUE
+ossl_cipher_final(VALUE self)
+{
+    EVP_CIPHER_CTX *ctx;
+    int out_len;
+    VALUE str;
+
+    GetCipher(self, ctx);
+    str = rb_str_new(0, EVP_CIPHER_CTX_block_size(ctx));
+    if (!EVP_CipherFinal_ex(ctx, (unsigned char *)RSTRING_PTR(str), &out_len))
+	ossl_raise(eCipherError, NULL);
+    assert(out_len <= RSTRING_LEN(str));
+    rb_str_set_len(str, out_len);
+
+    return str;
+}
+
+/*
+ *  call-seq:
+ *     cipher.name -> string
+ *
+ *  Returns the name of the cipher which may differ slightly from the original
+ *  name provided.
+ */
+static VALUE
+ossl_cipher_name(VALUE self)
+{
+    EVP_CIPHER_CTX *ctx;
+
+    GetCipher(self, ctx);
+
+    return rb_str_new2(EVP_CIPHER_name(EVP_CIPHER_CTX_cipher(ctx)));
+}
+
+/*
+ *  call-seq:
+ *     cipher.key = string -> string
+ *
+ *  Sets the cipher key. To generate a key, you should either use a secure
+ *  random byte string or, if the key is to be derived from a password, you
+ *  should rely on PBKDF2 functionality provided by OpenSSL::PKCS5. To
+ *  generate a secure random-based key, Cipher#random_key may be used.
+ *
+ *  Only call this method after calling Cipher#encrypt or Cipher#decrypt.
+ */
+static VALUE
+ossl_cipher_set_key(VALUE self, VALUE key)
+{
+    EVP_CIPHER_CTX *ctx;
+    int key_len;
+
+    StringValue(key);
+    GetCipher(self, ctx);
+
+    key_len = EVP_CIPHER_CTX_key_length(ctx);
+    if (RSTRING_LEN(key) != key_len)
+	ossl_raise(rb_eArgError, "key must be %d bytes", key_len);
+
+    if (EVP_CipherInit_ex(ctx, NULL, NULL, (unsigned char *)RSTRING_PTR(key), NULL, -1) != 1)
+	ossl_raise(eCipherError, NULL);
+
+    rb_ivar_set(self, id_key_set, Qtrue);
+
+    return key;
+}
+
+/*
+ *  call-seq:
+ *     cipher.iv = string -> string
+ *
+ *  Sets the cipher IV. Please note that since you should never be using ECB
+ *  mode, an IV is always explicitly required and should be set prior to
+ *  encryption. The IV itself can be safely transmitted in public, but it
+ *  should be unpredictable to prevent certain kinds of attacks. You may use
+ *  Cipher#random_iv to create a secure random IV.
+ *
+ *  Only call this method after calling Cipher#encrypt or Cipher#decrypt.
+ */
+static VALUE
+ossl_cipher_set_iv(VALUE self, VALUE iv)
+{
+    EVP_CIPHER_CTX *ctx;
+    int iv_len = 0;
+
+    StringValue(iv);
+    GetCipher(self, ctx);
+
+#if defined(HAVE_AUTHENTICATED_ENCRYPTION)
+    if (EVP_CIPHER_CTX_flags(ctx) & EVP_CIPH_FLAG_AEAD_CIPHER)
+	iv_len = (int)(VALUE)EVP_CIPHER_CTX_get_app_data(ctx);
+#endif
+    if (!iv_len)
+	iv_len = EVP_CIPHER_CTX_iv_length(ctx);
+    if (RSTRING_LEN(iv) != iv_len)
+	ossl_raise(rb_eArgError, "iv must be %d bytes", iv_len);
+
+    if (EVP_CipherInit_ex(ctx, NULL, NULL, NULL, (unsigned char *)RSTRING_PTR(iv), -1) != 1)
+	ossl_raise(eCipherError, NULL);
+
+    return iv;
+}
+
+/*
+ *  call-seq:
+ *     cipher.authenticated? -> true | false
+ *
+ *  Indicated whether this Cipher instance uses an Authenticated Encryption
+ *  mode.
+ */
+static VALUE
+ossl_cipher_is_authenticated(VALUE self)
+{
+    EVP_CIPHER_CTX *ctx;
+
+    GetCipher(self, ctx);
+
+#if defined(HAVE_AUTHENTICATED_ENCRYPTION)
+    return (EVP_CIPHER_CTX_flags(ctx) & EVP_CIPH_FLAG_AEAD_CIPHER) ? Qtrue : Qfalse;
+#else
+    return Qfalse;
+#endif
+}
+
+#ifdef HAVE_AUTHENTICATED_ENCRYPTION
+/*
+ *  call-seq:
+ *     cipher.auth_data = string -> string
+ *
+ *  Sets the cipher's additional authenticated data. This field must be
+ *  set when using AEAD cipher modes such as GCM or CCM. If no associated
+ *  data shall be used, this method must *still* be called with a value of "".
+ *  The contents of this field should be non-sensitive data which will be
+ *  added to the ciphertext to generate the authentication tag which validates
+ *  the contents of the ciphertext.
+ *
+ *  The AAD must be set prior to encryption or decryption. In encryption mode,
+ *  it must be set after calling Cipher#encrypt and setting Cipher#key= and
+ *  Cipher#iv=. When decrypting, the authenticated data must be set after key,
+ *  iv and especially *after* the authentication tag has been set. I.e. set it
+ *  only after calling Cipher#decrypt, Cipher#key=, Cipher#iv= and
+ *  Cipher#auth_tag= first.
+ */
+static VALUE
+ossl_cipher_set_auth_data(VALUE self, VALUE data)
+{
+    EVP_CIPHER_CTX *ctx;
+    unsigned char *in;
+    long in_len, out_len;
+
+    StringValue(data);
+
+    in = (unsigned char *) RSTRING_PTR(data);
+    in_len = RSTRING_LEN(data);
+
+    GetCipher(self, ctx);
+
+    if (!ossl_cipher_update_long(ctx, NULL, &out_len, in, in_len))
+        ossl_raise(eCipherError, "couldn't set additional authenticated data");
+
+    return data;
+}
+
+/*
+ *  call-seq:
+ *     cipher.auth_tag(tag_len = 16) -> String
+ *
+ *  Gets the authentication tag generated by Authenticated Encryption Cipher
+ *  modes (GCM for example). This tag may be stored along with the ciphertext,
+ *  then set on the decryption cipher to authenticate the contents of the
+ *  ciphertext against changes. If the optional integer parameter +tag_len+ is
+ *  given, the returned tag will be +tag_len+ bytes long. If the parameter is
+ *  omitted, the default length of 16 bytes or the length previously set by
+ *  #auth_tag_len= will be used. For maximum security, the longest possible
+ *  should be chosen.
+ *
+ *  The tag may only be retrieved after calling Cipher#final.
+ */
+static VALUE
+ossl_cipher_get_auth_tag(int argc, VALUE *argv, VALUE self)
+{
+    VALUE vtag_len, ret;
+    EVP_CIPHER_CTX *ctx;
+    int tag_len = 16;
+
+    rb_scan_args(argc, argv, "01", &vtag_len);
+    if (NIL_P(vtag_len))
+	vtag_len = rb_attr_get(self, id_auth_tag_len);
+    if (!NIL_P(vtag_len))
+	tag_len = NUM2INT(vtag_len);
+
+    GetCipher(self, ctx);
+
+    if (!(EVP_CIPHER_CTX_flags(ctx) & EVP_CIPH_FLAG_AEAD_CIPHER))
+	ossl_raise(eCipherError, "authentication tag not supported by this cipher");
+
+    ret = rb_str_new(NULL, tag_len);
+    if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, tag_len, RSTRING_PTR(ret)))
+	ossl_raise(eCipherError, "retrieving the authentication tag failed");
+
+    return ret;
+}
+
+/*
+ *  call-seq:
+ *     cipher.auth_tag = string -> string
+ *
+ *  Sets the authentication tag to verify the contents of the
+ *  ciphertext. The tag must be set after calling Cipher#decrypt,
+ *  Cipher#key= and Cipher#iv=, but before assigning the associated
+ *  authenticated data using Cipher#auth_data= and of course, before
+ *  decrypting any of the ciphertext. After all decryption is
+ *  performed, the tag is verified automatically in the call to
+ *  Cipher#final.
+ *
+ *  For OCB mode, the tag length must be supplied with #auth_tag_len=
+ *  beforehand.
+ */
+static VALUE
+ossl_cipher_set_auth_tag(VALUE self, VALUE vtag)
+{
+    EVP_CIPHER_CTX *ctx;
+    unsigned char *tag;
+    int tag_len;
+
+    StringValue(vtag);
+    tag = (unsigned char *) RSTRING_PTR(vtag);
+    tag_len = RSTRING_LENINT(vtag);
+
+    GetCipher(self, ctx);
+    if (!(EVP_CIPHER_CTX_flags(ctx) & EVP_CIPH_FLAG_AEAD_CIPHER))
+	ossl_raise(eCipherError, "authentication tag not supported by this cipher");
+
+    if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, tag_len, tag))
+	ossl_raise(eCipherError, "unable to set AEAD tag");
+
+    return vtag;
+}
+
+/*
+ *  call-seq:
+ *     cipher.auth_tag_len = Integer -> Integer
+ *
+ *  Sets the length of the authentication tag to be generated or to be given for
+ *  AEAD ciphers that requires it as in input parameter. Note that not all AEAD
+ *  ciphers support this method.
+ *
+ *  In OCB mode, the length must be supplied both when encrypting and when
+ *  decrypting, and must be before specifying an IV.
+ */
+static VALUE
+ossl_cipher_set_auth_tag_len(VALUE self, VALUE vlen)
+{
+    int tag_len = NUM2INT(vlen);
+    EVP_CIPHER_CTX *ctx;
+
+    GetCipher(self, ctx);
+    if (!(EVP_CIPHER_CTX_flags(ctx) & EVP_CIPH_FLAG_AEAD_CIPHER))
+	ossl_raise(eCipherError, "AEAD not supported by this cipher");
+
+    if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, tag_len, NULL))
+	ossl_raise(eCipherError, "unable to set authentication tag length");
+
+    /* for #auth_tag */
+    rb_ivar_set(self, id_auth_tag_len, INT2NUM(tag_len));
+
+    return vlen;
+}
+
+/*
+ * call-seq:
+ *   cipher.iv_len = integer -> integer
+ *
+ * Sets the IV/nonce length of the Cipher. Normally block ciphers don't allow
+ * changing the IV length, but some make use of IV for 'nonce'. You may need
+ * this for interoperability with other applications.
+ */
+static VALUE
+ossl_cipher_set_iv_length(VALUE self, VALUE iv_length)
+{
+    int len = NUM2INT(iv_length);
+    EVP_CIPHER_CTX *ctx;
+
+    GetCipher(self, ctx);
+    if (!(EVP_CIPHER_CTX_flags(ctx) & EVP_CIPH_FLAG_AEAD_CIPHER))
+	ossl_raise(eCipherError, "cipher does not support AEAD");
+
+    if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, len, NULL))
+	ossl_raise(eCipherError, "unable to set IV length");
+
+    /*
+     * EVP_CIPHER_CTX_iv_length() returns the default length. So we need to save
+     * the length somewhere. Luckily currently we aren't using app_data.
+     */
+    EVP_CIPHER_CTX_set_app_data(ctx, (void *)(VALUE)len);
+
+    return iv_length;
+}
+#else
+#define ossl_cipher_set_auth_data rb_f_notimplement
+#define ossl_cipher_get_auth_tag rb_f_notimplement
+#define ossl_cipher_set_auth_tag rb_f_notimplement
+#define ossl_cipher_set_auth_tag_len rb_f_notimplement
+#define ossl_cipher_set_iv_length rb_f_notimplement
+#endif
+
+/*
+ *  call-seq:
+ *     cipher.key_len = integer -> integer
+ *
+ *  Sets the key length of the cipher.  If the cipher is a fixed length cipher
+ *  then attempting to set the key length to any value other than the fixed
+ *  value is an error.
+ *
+ *  Under normal circumstances you do not need to call this method (and probably shouldn't).
+ *
+ *  See EVP_CIPHER_CTX_set_key_length for further information.
+ */
+static VALUE
+ossl_cipher_set_key_length(VALUE self, VALUE key_length)
+{
+    int len = NUM2INT(key_length);
+    EVP_CIPHER_CTX *ctx;
+
+    GetCipher(self, ctx);
+    if (EVP_CIPHER_CTX_set_key_length(ctx, len) != 1)
+        ossl_raise(eCipherError, NULL);
+
+    return key_length;
+}
+
+/*
+ *  call-seq:
+ *     cipher.padding = integer -> integer
+ *
+ *  Enables or disables padding. By default encryption operations are padded using standard block padding and the
+ *  padding is checked and removed when decrypting. If the pad parameter is zero then no padding is performed, the
+ *  total amount of data encrypted or decrypted must then be a multiple of the block size or an error will occur.
+ *
+ *  See EVP_CIPHER_CTX_set_padding for further information.
+ */
+static VALUE
+ossl_cipher_set_padding(VALUE self, VALUE padding)
+{
+    EVP_CIPHER_CTX *ctx;
+    int pad = NUM2INT(padding);
+
+    GetCipher(self, ctx);
+    if (EVP_CIPHER_CTX_set_padding(ctx, pad) != 1)
+	ossl_raise(eCipherError, NULL);
+    return padding;
+}
+
+/*
+ *  call-seq:
+ *     cipher.key_len -> integer
+ *
+ *  Returns the key length in bytes of the Cipher.
+ */
+static VALUE
+ossl_cipher_key_length(VALUE self)
+{
+    EVP_CIPHER_CTX *ctx;
+
+    GetCipher(self, ctx);
+
+    return INT2NUM(EVP_CIPHER_CTX_key_length(ctx));
+}
+
+/*
+ *  call-seq:
+ *     cipher.iv_len -> integer
+ *
+ *  Returns the expected length in bytes for an IV for this Cipher.
+ */
+static VALUE
+ossl_cipher_iv_length(VALUE self)
+{
+    EVP_CIPHER_CTX *ctx;
+    int len = 0;
+
+    GetCipher(self, ctx);
+#if defined(HAVE_AUTHENTICATED_ENCRYPTION)
+    if (EVP_CIPHER_CTX_flags(ctx) & EVP_CIPH_FLAG_AEAD_CIPHER)
+	len = (int)(VALUE)EVP_CIPHER_CTX_get_app_data(ctx);
+#endif
+    if (!len)
+	len = EVP_CIPHER_CTX_iv_length(ctx);
+
+    return INT2NUM(len);
+}
+
+/*
+ *  call-seq:
+ *     cipher.block_size -> integer
+ *
+ *  Returns the size in bytes of the blocks on which this Cipher operates on.
+ */
+static VALUE
+ossl_cipher_block_size(VALUE self)
+{
+    EVP_CIPHER_CTX *ctx;
+
+    GetCipher(self, ctx);
+
+    return INT2NUM(EVP_CIPHER_CTX_block_size(ctx));
+}
+
+/*
+ * INIT
+ */
+void
+Init_ossl_cipher(void)
+{
+#if 0
+    mOSSL = rb_define_module("OpenSSL");
+    eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError);
+#endif
+
+    /* Document-class: OpenSSL::Cipher
+     *
+     * Provides symmetric algorithms for encryption and decryption. The
+     * algorithms that are available depend on the particular version
+     * of OpenSSL that is installed.
+     *
+     * === Listing all supported algorithms
+     *
+     * A list of supported algorithms can be obtained by
+     *
+     *   puts OpenSSL::Cipher.ciphers
+     *
+     * === Instantiating a Cipher
+     *
+     * There are several ways to create a Cipher instance. Generally, a
+     * Cipher algorithm is categorized by its name, the key length in bits
+     * and the cipher mode to be used. The most generic way to create a
+     * Cipher is the following
+     *
+     *   cipher = OpenSSL::Cipher.new('<name>-<key length>-<mode>')
+     *
+     * That is, a string consisting of the hyphenated concatenation of the
+     * individual components name, key length and mode. Either all uppercase
+     * or all lowercase strings may be used, for example:
+     *
+     *  cipher = OpenSSL::Cipher.new('AES-128-CBC')
+     *
+     * For each algorithm supported, there is a class defined under the
+     * Cipher class that goes by the name of the cipher, e.g. to obtain an
+     * instance of AES, you could also use
+     *
+     *   # these are equivalent
+     *   cipher = OpenSSL::Cipher::AES.new(128, :CBC)
+     *   cipher = OpenSSL::Cipher::AES.new(128, 'CBC')
+     *   cipher = OpenSSL::Cipher::AES.new('128-CBC')
+     *
+     * Finally, due to its wide-spread use, there are also extra classes
+     * defined for the different key sizes of AES
+     *
+     *   cipher = OpenSSL::Cipher::AES128.new(:CBC)
+     *   cipher = OpenSSL::Cipher::AES192.new(:CBC)
+     *   cipher = OpenSSL::Cipher::AES256.new(:CBC)
+     *
+     * === Choosing either encryption or decryption mode
+     *
+     * Encryption and decryption are often very similar operations for
+     * symmetric algorithms, this is reflected by not having to choose
+     * different classes for either operation, both can be done using the
+     * same class. Still, after obtaining a Cipher instance, we need to
+     * tell the instance what it is that we intend to do with it, so we
+     * need to call either
+     *
+     *   cipher.encrypt
+     *
+     * or
+     *
+     *   cipher.decrypt
+     *
+     * on the Cipher instance. This should be the first call after creating
+     * the instance, otherwise configuration that has already been set could
+     * get lost in the process.
+     *
+     * === Choosing a key
+     *
+     * Symmetric encryption requires a key that is the same for the encrypting
+     * and for the decrypting party and after initial key establishment should
+     * be kept as private information. There are a lot of ways to create
+     * insecure keys, the most notable is to simply take a password as the key
+     * without processing the password further. A simple and secure way to
+     * create a key for a particular Cipher is
+     *
+     *  cipher = OpenSSL::AES256.new(:CFB)
+     *  cipher.encrypt
+     *  key = cipher.random_key # also sets the generated key on the Cipher
+     *
+     * If you absolutely need to use passwords as encryption keys, you
+     * should use Password-Based Key Derivation Function 2 (PBKDF2) by
+     * generating the key with the help of the functionality provided by
+     * OpenSSL::PKCS5.pbkdf2_hmac_sha1 or OpenSSL::PKCS5.pbkdf2_hmac.
+     *
+     * Although there is Cipher#pkcs5_keyivgen, its use is deprecated and
+     * it should only be used in legacy applications because it does not use
+     * the newer PKCS#5 v2 algorithms.
+     *
+     * === Choosing an IV
+     *
+     * The cipher modes CBC, CFB, OFB and CTR all need an "initialization
+     * vector", or short, IV. ECB mode is the only mode that does not require
+     * an IV, but there is almost no legitimate use case for this mode
+     * because of the fact that it does not sufficiently hide plaintext
+     * patterns. Therefore
+     *
+     * <b>You should never use ECB mode unless you are absolutely sure that
+     * you absolutely need it</b>
+     *
+     * Because of this, you will end up with a mode that explicitly requires
+     * an IV in any case. Although the IV can be seen as public information,
+     * i.e. it may be transmitted in public once generated, it should still
+     * stay unpredictable to prevent certain kinds of attacks. Therefore,
+     * ideally
+     *
+     * <b>Always create a secure random IV for every encryption of your
+     * Cipher</b>
+     *
+     * A new, random IV should be created for every encryption of data. Think
+     * of the IV as a nonce (number used once) - it's public but random and
+     * unpredictable. A secure random IV can be created as follows
+     *
+     *   cipher = ...
+     *   cipher.encrypt
+     *   key = cipher.random_key
+     *   iv = cipher.random_iv # also sets the generated IV on the Cipher
+     *
+     * Although the key is generally a random value, too, it is a bad choice
+     * as an IV. There are elaborate ways how an attacker can take advantage
+     * of such an IV. As a general rule of thumb, exposing the key directly
+     * or indirectly should be avoided at all cost and exceptions only be
+     * made with good reason.
+     *
+     * === Calling Cipher#final
+     *
+     * ECB (which should not be used) and CBC are both block-based modes.
+     * This means that unlike for the other streaming-based modes, they
+     * operate on fixed-size blocks of data, and therefore they require a
+     * "finalization" step to produce or correctly decrypt the last block of
+     * data by appropriately handling some form of padding. Therefore it is
+     * essential to add the output of OpenSSL::Cipher#final to your
+     * encryption/decryption buffer or you will end up with decryption errors
+     * or truncated data.
+     *
+     * Although this is not really necessary for streaming-mode ciphers, it is
+     * still recommended to apply the same pattern of adding the output of
+     * Cipher#final there as well - it also enables you to switch between
+     * modes more easily in the future.
+     *
+     * === Encrypting and decrypting some data
+     *
+     *   data = "Very, very confidential data"
+     *
+     *   cipher = OpenSSL::Cipher::AES.new(128, :CBC)
+     *   cipher.encrypt
+     *   key = cipher.random_key
+     *   iv = cipher.random_iv
+     *
+     *   encrypted = cipher.update(data) + cipher.final
+     *   ...
+     *   decipher = OpenSSL::Cipher::AES.new(128, :CBC)
+     *   decipher.decrypt
+     *   decipher.key = key
+     *   decipher.iv = iv
+     *
+     *   plain = decipher.update(encrypted) + decipher.final
+     *
+     *   puts data == plain #=> true
+     *
+     * === Authenticated Encryption and Associated Data (AEAD)
+     *
+     * If the OpenSSL version used supports it, an Authenticated Encryption
+     * mode (such as GCM or CCM) should always be preferred over any
+     * unauthenticated mode. Currently, OpenSSL supports AE only in combination
+     * with Associated Data (AEAD) where additional associated data is included
+     * in the encryption process to compute a tag at the end of the encryption.
+     * This tag will also be used in the decryption process and by verifying
+     * its validity, the authenticity of a given ciphertext is established.
+     *
+     * This is superior to unauthenticated modes in that it allows to detect
+     * if somebody effectively changed the ciphertext after it had been
+     * encrypted. This prevents malicious modifications of the ciphertext that
+     * could otherwise be exploited to modify ciphertexts in ways beneficial to
+     * potential attackers.
+     *
+     * An associated data is used where there is additional information, such as
+     * headers or some metadata, that must be also authenticated but not
+     * necessarily need to be encrypted. If no associated data is needed for
+     * encryption and later decryption, the OpenSSL library still requires a
+     * value to be set - "" may be used in case none is available.
+     *
+     * An example using the GCM (Galois/Counter Mode). You have 16 bytes +key+,
+     * 12 bytes (96 bits) +nonce+ and the associated data +auth_data+. Be sure
+     * not to reuse the +key+ and +nonce+ pair. Reusing an nonce ruins the
+     * security guarantees of GCM mode.
+     *
+     *   cipher = OpenSSL::Cipher::AES.new(128, :GCM).encrypt
+     *   cipher.key = key
+     *   cipher.iv = nonce
+     *   cipher.auth_data = auth_data
+     *
+     *   encrypted = cipher.update(data) + cipher.final
+     *   tag = cipher.auth_tag # produces 16 bytes tag by default
+     *
+     * Now you are the receiver. You know the +key+ and have received +nonce+,
+     * +auth_data+, +encrypted+ and +tag+ through an untrusted network. Note
+     * that GCM accepts an arbitrary length tag between 1 and 16 bytes. You may
+     * additionally need to check that the received tag has the correct length,
+     * or you allow attackers to forge a valid single byte tag for the tampered
+     * ciphertext with a probability of 1/256.
+     *
+     *   raise "tag is truncated!" unless tag.bytesize == 16
+     *   decipher = OpenSSL::Cipher::AES.new(128, :GCM).decrypt
+     *   decipher.key = key
+     *   decipher.iv = nonce
+     *   decipher.auth_tag = tag
+     *   decipher.auth_data = auth_data
+     *
+     *   decrypted = decipher.update(encrypted) + decipher.final
+     *
+     *   puts data == decrypted #=> true
+     */
+    cCipher = rb_define_class_under(mOSSL, "Cipher", rb_cObject);
+    eCipherError = rb_define_class_under(cCipher, "CipherError", eOSSLError);
+
+    rb_define_alloc_func(cCipher, ossl_cipher_alloc);
+    rb_define_copy_func(cCipher, ossl_cipher_copy);
+    rb_define_module_function(cCipher, "ciphers", ossl_s_ciphers, 0);
+    rb_define_method(cCipher, "initialize", ossl_cipher_initialize, 1);
+    rb_define_method(cCipher, "reset", ossl_cipher_reset, 0);
+    rb_define_method(cCipher, "encrypt", ossl_cipher_encrypt, -1);
+    rb_define_method(cCipher, "decrypt", ossl_cipher_decrypt, -1);
+    rb_define_method(cCipher, "pkcs5_keyivgen", ossl_cipher_pkcs5_keyivgen, -1);
+    rb_define_method(cCipher, "update", ossl_cipher_update, -1);
+    rb_define_method(cCipher, "final", ossl_cipher_final, 0);
+    rb_define_method(cCipher, "name", ossl_cipher_name, 0);
+    rb_define_method(cCipher, "key=", ossl_cipher_set_key, 1);
+    rb_define_method(cCipher, "auth_data=", ossl_cipher_set_auth_data, 1);
+    rb_define_method(cCipher, "auth_tag=", ossl_cipher_set_auth_tag, 1);
+    rb_define_method(cCipher, "auth_tag", ossl_cipher_get_auth_tag, -1);
+    rb_define_method(cCipher, "auth_tag_len=", ossl_cipher_set_auth_tag_len, 1);
+    rb_define_method(cCipher, "authenticated?", ossl_cipher_is_authenticated, 0);
+    rb_define_method(cCipher, "key_len=", ossl_cipher_set_key_length, 1);
+    rb_define_method(cCipher, "key_len", ossl_cipher_key_length, 0);
+    rb_define_method(cCipher, "iv=", ossl_cipher_set_iv, 1);
+    rb_define_method(cCipher, "iv_len=", ossl_cipher_set_iv_length, 1);
+    rb_define_method(cCipher, "iv_len", ossl_cipher_iv_length, 0);
+    rb_define_method(cCipher, "block_size", ossl_cipher_block_size, 0);
+    rb_define_method(cCipher, "padding=", ossl_cipher_set_padding, 1);
+
+    id_auth_tag_len = rb_intern_const("auth_tag_len");
+    id_key_set = rb_intern_const("key_set");
+}
diff --git a/ext/openssl/ossl_cipher.h b/ext/openssl/ossl_cipher.h
new file mode 100644
index 0000000..c444089
--- /dev/null
+++ b/ext/openssl/ossl_cipher.h
@@ -0,0 +1,20 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2001-2002  Michal Rokos <m.rokos at sh.cvut.cz>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#if !defined(_OSSL_CIPHER_H_)
+#define _OSSL_CIPHER_H_
+
+extern VALUE cCipher;
+extern VALUE eCipherError;
+
+const EVP_CIPHER *GetCipherPtr(VALUE);
+VALUE ossl_cipher_new(const EVP_CIPHER *);
+void Init_ossl_cipher(void);
+
+#endif /* _OSSL_CIPHER_H_ */
diff --git a/ext/openssl/ossl_config.c b/ext/openssl/ossl_config.c
new file mode 100644
index 0000000..ebf6ae2
--- /dev/null
+++ b/ext/openssl/ossl_config.c
@@ -0,0 +1,89 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2001-2002  Michal Rokos <m.rokos at sh.cvut.cz>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#include "ossl.h"
+
+
+/*
+ * Classes
+ */
+VALUE cConfig;
+/* Document-class: OpenSSL::ConfigError
+ *
+ * General error for openssl library configuration files. Including formatting,
+ * parsing errors, etc.
+ */
+VALUE eConfigError;
+
+/*
+ * Public
+ */
+
+/*
+ * DupConfigPtr is a public C-level function for getting OpenSSL CONF struct
+ * from an OpenSSL::Config(eConfig) instance.  We decided to implement
+ * OpenSSL::Config in Ruby level but we need to pass native CONF struct for
+ * some OpenSSL features such as X509V3_EXT_*.
+ */
+CONF *
+DupConfigPtr(VALUE obj)
+{
+    CONF *conf;
+    VALUE str;
+    BIO *bio;
+    long eline = -1;
+
+    OSSL_Check_Kind(obj, cConfig);
+    str = rb_funcall(obj, rb_intern("to_s"), 0);
+    bio = ossl_obj2bio(str);
+    conf = NCONF_new(NULL);
+    if(!conf){
+	BIO_free(bio);
+	ossl_raise(eConfigError, NULL);
+    }
+    if(!NCONF_load_bio(conf, bio, &eline)){
+	BIO_free(bio);
+	NCONF_free(conf);
+	if (eline <= 0)
+	    ossl_raise(eConfigError, "wrong config format");
+	else
+	    ossl_raise(eConfigError, "error in line %d", eline);
+    }
+    BIO_free(bio);
+
+    return conf;
+}
+
+/* Document-const: DEFAULT_CONFIG_FILE
+ *
+ * The default system configuration file for openssl
+ */
+
+/*
+ * INIT
+ */
+void
+Init_ossl_config(void)
+{
+    char *default_config_file;
+
+#if 0
+    mOSSL = rb_define_module("OpenSSL");
+    eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError);
+#endif
+
+    eConfigError = rb_define_class_under(mOSSL, "ConfigError", eOSSLError);
+    cConfig = rb_define_class_under(mOSSL, "Config", rb_cObject);
+
+    default_config_file = CONF_get1_default_config_file();
+    rb_define_const(cConfig, "DEFAULT_CONFIG_FILE",
+		    rb_str_new2(default_config_file));
+    OPENSSL_free(default_config_file);
+    /* methods are defined by openssl/config.rb */
+}
diff --git a/ext/openssl/ossl_config.h b/ext/openssl/ossl_config.h
new file mode 100644
index 0000000..627d297
--- /dev/null
+++ b/ext/openssl/ossl_config.h
@@ -0,0 +1,19 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2001-2002  Michal Rokos <m.rokos at sh.cvut.cz>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#if !defined(_OSSL_CONFIG_H_)
+#define _OSSL_CONFIG_H_
+
+extern VALUE cConfig;
+extern VALUE eConfigError;
+
+CONF* DupConfigPtr(VALUE obj);
+void Init_ossl_config(void);
+
+#endif /* _OSSL_CONFIG_H_ */
diff --git a/ext/openssl/ossl_digest.c b/ext/openssl/ossl_digest.c
new file mode 100644
index 0000000..fdafda0
--- /dev/null
+++ b/ext/openssl/ossl_digest.c
@@ -0,0 +1,460 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2001-2002  Michal Rokos <m.rokos at sh.cvut.cz>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#include "ossl.h"
+
+#define GetDigest(obj, ctx) do { \
+    TypedData_Get_Struct((obj), EVP_MD_CTX, &ossl_digest_type, (ctx)); \
+    if (!(ctx)) { \
+	ossl_raise(rb_eRuntimeError, "Digest CTX wasn't initialized!"); \
+    } \
+} while (0)
+#define SafeGetDigest(obj, ctx) do { \
+    OSSL_Check_Kind((obj), cDigest); \
+    GetDigest((obj), (ctx)); \
+} while (0)
+
+/*
+ * Classes
+ */
+VALUE cDigest;
+VALUE eDigestError;
+
+static VALUE ossl_digest_alloc(VALUE klass);
+
+static void
+ossl_digest_free(void *ctx)
+{
+    EVP_MD_CTX_destroy(ctx);
+}
+
+static const rb_data_type_t ossl_digest_type = {
+    "OpenSSL/Digest",
+    {
+	0, ossl_digest_free,
+    },
+    0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
+};
+
+/*
+ * Public
+ */
+const EVP_MD *
+GetDigestPtr(VALUE obj)
+{
+    const EVP_MD *md;
+    ASN1_OBJECT *oid = NULL;
+
+    if (RB_TYPE_P(obj, T_STRING)) {
+    	const char *name = StringValueCStr(obj);
+
+	md = EVP_get_digestbyname(name);
+	if (!md) {
+	    oid = OBJ_txt2obj(name, 0);
+	    md = EVP_get_digestbyobj(oid);
+	    ASN1_OBJECT_free(oid);
+	}
+	if(!md)
+            ossl_raise(rb_eRuntimeError, "Unsupported digest algorithm (%"PRIsVALUE").", obj);
+    } else {
+        EVP_MD_CTX *ctx;
+
+        SafeGetDigest(obj, ctx);
+
+        md = EVP_MD_CTX_md(ctx);
+    }
+
+    return md;
+}
+
+VALUE
+ossl_digest_new(const EVP_MD *md)
+{
+    VALUE ret;
+    EVP_MD_CTX *ctx;
+
+    ret = ossl_digest_alloc(cDigest);
+    ctx = EVP_MD_CTX_new();
+    if (!ctx)
+	ossl_raise(eDigestError, "EVP_MD_CTX_new");
+    RTYPEDDATA_DATA(ret) = ctx;
+
+    if (!EVP_DigestInit_ex(ctx, md, NULL))
+	ossl_raise(eDigestError, "Digest initialization failed");
+
+    return ret;
+}
+
+/*
+ * Private
+ */
+static VALUE
+ossl_digest_alloc(VALUE klass)
+{
+    return TypedData_Wrap_Struct(klass, &ossl_digest_type, 0);
+}
+
+VALUE ossl_digest_update(VALUE, VALUE);
+
+/*
+ *  call-seq:
+ *     Digest.new(string [, data]) -> Digest
+ *
+ * Creates a Digest instance based on +string+, which is either the ln
+ * (long name) or sn (short name) of a supported digest algorithm.
+ *
+ * If +data+ (a +String+) is given, it is used as the initial input to the
+ * Digest instance, i.e.
+ *
+ *   digest = OpenSSL::Digest.new('sha256', 'digestdata')
+ *
+ * is equal to
+ *
+ *   digest = OpenSSL::Digest.new('sha256')
+ *   digest.update('digestdata')
+ */
+static VALUE
+ossl_digest_initialize(int argc, VALUE *argv, VALUE self)
+{
+    EVP_MD_CTX *ctx;
+    const EVP_MD *md;
+    VALUE type, data;
+
+    rb_scan_args(argc, argv, "11", &type, &data);
+    md = GetDigestPtr(type);
+    if (!NIL_P(data)) StringValue(data);
+
+    TypedData_Get_Struct(self, EVP_MD_CTX, &ossl_digest_type, ctx);
+    if (!ctx) {
+	RTYPEDDATA_DATA(self) = ctx = EVP_MD_CTX_new();
+	if (!ctx)
+	    ossl_raise(eDigestError, "EVP_MD_CTX_new");
+    }
+
+    if (!EVP_DigestInit_ex(ctx, md, NULL))
+	ossl_raise(eDigestError, "Digest initialization failed");
+
+    if (!NIL_P(data)) return ossl_digest_update(self, data);
+    return self;
+}
+
+static VALUE
+ossl_digest_copy(VALUE self, VALUE other)
+{
+    EVP_MD_CTX *ctx1, *ctx2;
+
+    rb_check_frozen(self);
+    if (self == other) return self;
+
+    TypedData_Get_Struct(self, EVP_MD_CTX, &ossl_digest_type, ctx1);
+    if (!ctx1) {
+	RTYPEDDATA_DATA(self) = ctx1 = EVP_MD_CTX_new();
+	if (!ctx1)
+	    ossl_raise(eDigestError, "EVP_MD_CTX_new");
+    }
+    SafeGetDigest(other, ctx2);
+
+    if (!EVP_MD_CTX_copy(ctx1, ctx2)) {
+	ossl_raise(eDigestError, NULL);
+    }
+    return self;
+}
+
+/*
+ *  call-seq:
+ *     digest.reset -> self
+ *
+ * Resets the Digest in the sense that any Digest#update that has been
+ * performed is abandoned and the Digest is set to its initial state again.
+ *
+ */
+static VALUE
+ossl_digest_reset(VALUE self)
+{
+    EVP_MD_CTX *ctx;
+
+    GetDigest(self, ctx);
+    if (EVP_DigestInit_ex(ctx, EVP_MD_CTX_md(ctx), NULL) != 1) {
+	ossl_raise(eDigestError, "Digest initialization failed.");
+    }
+
+    return self;
+}
+
+/*
+ *  call-seq:
+ *     digest.update(string) -> aString
+ *
+ * Not every message digest can be computed in one single pass. If a message
+ * digest is to be computed from several subsequent sources, then each may
+ * be passed individually to the Digest instance.
+ *
+ * === Example
+ *   digest = OpenSSL::Digest::SHA256.new
+ *   digest.update('First input')
+ *   digest << 'Second input' # equivalent to digest.update('Second input')
+ *   result = digest.digest
+ *
+ */
+VALUE
+ossl_digest_update(VALUE self, VALUE data)
+{
+    EVP_MD_CTX *ctx;
+
+    StringValue(data);
+    GetDigest(self, ctx);
+
+    if (!EVP_DigestUpdate(ctx, RSTRING_PTR(data), RSTRING_LEN(data)))
+	ossl_raise(eDigestError, "EVP_DigestUpdate");
+
+    return self;
+}
+
+/*
+ *  call-seq:
+ *      digest.finish -> aString
+ *
+ */
+static VALUE
+ossl_digest_finish(int argc, VALUE *argv, VALUE self)
+{
+    EVP_MD_CTX *ctx;
+    VALUE str;
+    int out_len;
+
+    GetDigest(self, ctx);
+    rb_scan_args(argc, argv, "01", &str);
+    out_len = EVP_MD_CTX_size(ctx);
+
+    if (NIL_P(str)) {
+        str = rb_str_new(NULL, out_len);
+    } else {
+        StringValue(str);
+        rb_str_resize(str, out_len);
+    }
+
+    if (!EVP_DigestFinal_ex(ctx, (unsigned char *)RSTRING_PTR(str), NULL))
+	ossl_raise(eDigestError, "EVP_DigestFinal_ex");
+
+    return str;
+}
+
+/*
+ *  call-seq:
+ *      digest.name -> string
+ *
+ * Returns the sn of this Digest algorithm.
+ *
+ * === Example
+ *   digest = OpenSSL::Digest::SHA512.new
+ *   puts digest.name # => SHA512
+ *
+ */
+static VALUE
+ossl_digest_name(VALUE self)
+{
+    EVP_MD_CTX *ctx;
+
+    GetDigest(self, ctx);
+
+    return rb_str_new2(EVP_MD_name(EVP_MD_CTX_md(ctx)));
+}
+
+/*
+ *  call-seq:
+ *      digest.digest_length -> integer
+ *
+ * Returns the output size of the digest, i.e. the length in bytes of the
+ * final message digest result.
+ *
+ * === Example
+ *   digest = OpenSSL::Digest::SHA1.new
+ *   puts digest.digest_length # => 20
+ *
+ */
+static VALUE
+ossl_digest_size(VALUE self)
+{
+    EVP_MD_CTX *ctx;
+
+    GetDigest(self, ctx);
+
+    return INT2NUM(EVP_MD_CTX_size(ctx));
+}
+
+/*
+ *  call-seq:
+ *      digest.block_length -> integer
+ *
+ * Returns the block length of the digest algorithm, i.e. the length in bytes
+ * of an individual block. Most modern algorithms partition a message to be
+ * digested into a sequence of fix-sized blocks that are processed
+ * consecutively.
+ *
+ * === Example
+ *   digest = OpenSSL::Digest::SHA1.new
+ *   puts digest.block_length # => 64
+ */
+static VALUE
+ossl_digest_block_length(VALUE self)
+{
+    EVP_MD_CTX *ctx;
+
+    GetDigest(self, ctx);
+
+    return INT2NUM(EVP_MD_CTX_block_size(ctx));
+}
+
+/*
+ * INIT
+ */
+void
+Init_ossl_digest(void)
+{
+    rb_require("digest");
+
+#if 0
+    mOSSL = rb_define_module("OpenSSL");
+    eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError);
+#endif
+
+    /* Document-class: OpenSSL::Digest
+     *
+     * OpenSSL::Digest allows you to compute message digests (sometimes
+     * interchangeably called "hashes") of arbitrary data that are
+     * cryptographically secure, i.e. a Digest implements a secure one-way
+     * function.
+     *
+     * One-way functions offer some useful properties. E.g. given two
+     * distinct inputs the probability that both yield the same output
+     * is highly unlikely. Combined with the fact that every message digest
+     * algorithm has a fixed-length output of just a few bytes, digests are
+     * often used to create unique identifiers for arbitrary data. A common
+     * example is the creation of a unique id for binary documents that are
+     * stored in a database.
+     *
+     * Another useful characteristic of one-way functions (and thus the name)
+     * is that given a digest there is no indication about the original
+     * data that produced it, i.e. the only way to identify the original input
+     * is to "brute-force" through every possible combination of inputs.
+     *
+     * These characteristics make one-way functions also ideal companions
+     * for public key signature algorithms: instead of signing an entire
+     * document, first a hash of the document is produced with a considerably
+     * faster message digest algorithm and only the few bytes of its output
+     * need to be signed using the slower public key algorithm. To validate
+     * the integrity of a signed document, it suffices to re-compute the hash
+     * and verify that it is equal to that in the signature.
+     *
+     * Among the supported message digest algorithms are:
+     * * SHA, SHA1, SHA224, SHA256, SHA384 and SHA512
+     * * MD2, MD4, MDC2 and MD5
+     * * RIPEMD160
+     * * DSS, DSS1 (Pseudo algorithms to be used for DSA signatures. DSS is
+     *   equal to SHA and DSS1 is equal to SHA1)
+     *
+     * For each of these algorithms, there is a sub-class of Digest that
+     * can be instantiated as simply as e.g.
+     *
+     *   digest = OpenSSL::Digest::SHA1.new
+     *
+     * === Mapping between Digest class and sn/ln
+     *
+     * The sn (short names) and ln (long names) are defined in
+     * <openssl/object.h> and <openssl/obj_mac.h>. They are textual
+     * representations of ASN.1 OBJECT IDENTIFIERs. Each supported digest
+     * algorithm has an OBJECT IDENTIFIER associated to it and those again
+     * have short/long names assigned to them.
+     * E.g. the OBJECT IDENTIFIER for SHA-1 is 1.3.14.3.2.26 and its
+     * sn is "SHA1" and its ln is "sha1".
+     * ==== MD2
+     * * sn: MD2
+     * * ln: md2
+     * ==== MD4
+     * * sn: MD4
+     * * ln: md4
+     * ==== MD5
+     * * sn: MD5
+     * * ln: md5
+     * ==== SHA
+     * * sn: SHA
+     * * ln: SHA
+     * ==== SHA-1
+     * * sn: SHA1
+     * * ln: sha1
+     * ==== SHA-224
+     * * sn: SHA224
+     * * ln: sha224
+     * ==== SHA-256
+     * * sn: SHA256
+     * * ln: sha256
+     * ==== SHA-384
+     * * sn: SHA384
+     * * ln: sha384
+     * ==== SHA-512
+     * * sn: SHA512
+     * * ln: sha512
+     *
+     * "Breaking" a message digest algorithm means defying its one-way
+     * function characteristics, i.e. producing a collision or finding a way
+     * to get to the original data by means that are more efficient than
+     * brute-forcing etc. Most of the supported digest algorithms can be
+     * considered broken in this sense, even the very popular MD5 and SHA1
+     * algorithms. Should security be your highest concern, then you should
+     * probably rely on SHA224, SHA256, SHA384 or SHA512.
+     *
+     * === Hashing a file
+     *
+     *   data = File.read('document')
+     *   sha256 = OpenSSL::Digest::SHA256.new
+     *   digest = sha256.digest(data)
+     *
+     * === Hashing several pieces of data at once
+     *
+     *   data1 = File.read('file1')
+     *   data2 = File.read('file2')
+     *   data3 = File.read('file3')
+     *   sha256 = OpenSSL::Digest::SHA256.new
+     *   sha256 << data1
+     *   sha256 << data2
+     *   sha256 << data3
+     *   digest = sha256.digest
+     *
+     * === Reuse a Digest instance
+     *
+     *   data1 = File.read('file1')
+     *   sha256 = OpenSSL::Digest::SHA256.new
+     *   digest1 = sha256.digest(data1)
+     *
+     *   data2 = File.read('file2')
+     *   sha256.reset
+     *   digest2 = sha256.digest(data2)
+     *
+     */
+    cDigest = rb_define_class_under(mOSSL, "Digest", rb_path2class("Digest::Class"));
+    /* Document-class: OpenSSL::Digest::DigestError
+     *
+     * Generic Exception class that is raised if an error occurs during a
+     * Digest operation.
+     */
+    eDigestError = rb_define_class_under(cDigest, "DigestError", eOSSLError);
+
+    rb_define_alloc_func(cDigest, ossl_digest_alloc);
+
+    rb_define_method(cDigest, "initialize", ossl_digest_initialize, -1);
+    rb_define_copy_func(cDigest, ossl_digest_copy);
+    rb_define_method(cDigest, "reset", ossl_digest_reset, 0);
+    rb_define_method(cDigest, "update", ossl_digest_update, 1);
+    rb_define_alias(cDigest, "<<", "update");
+    rb_define_private_method(cDigest, "finish", ossl_digest_finish, -1);
+    rb_define_method(cDigest, "digest_length", ossl_digest_size, 0);
+    rb_define_method(cDigest, "block_length", ossl_digest_block_length, 0);
+
+    rb_define_method(cDigest, "name", ossl_digest_name, 0);
+}
diff --git a/ext/openssl/ossl_digest.h b/ext/openssl/ossl_digest.h
new file mode 100644
index 0000000..512f7d3
--- /dev/null
+++ b/ext/openssl/ossl_digest.h
@@ -0,0 +1,20 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2001-2002  Michal Rokos <m.rokos at sh.cvut.cz>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#if !defined(_OSSL_DIGEST_H_)
+#define _OSSL_DIGEST_H_
+
+extern VALUE cDigest;
+extern VALUE eDigestError;
+
+const EVP_MD *GetDigestPtr(VALUE);
+VALUE ossl_digest_new(const EVP_MD *);
+void Init_ossl_digest(void);
+
+#endif /* _OSSL_DIGEST_H_ */
diff --git a/ext/openssl/ossl_engine.c b/ext/openssl/ossl_engine.c
new file mode 100644
index 0000000..e840bfd
--- /dev/null
+++ b/ext/openssl/ossl_engine.c
@@ -0,0 +1,563 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2003  GOTOU Yuuzou <gotoyuzo at notwork.org>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#include "ossl.h"
+
+#if !defined(OPENSSL_NO_ENGINE)
+
+#define NewEngine(klass) \
+    TypedData_Wrap_Struct((klass), &ossl_engine_type, 0)
+#define SetEngine(obj, engine) do { \
+    if (!(engine)) { \
+	ossl_raise(rb_eRuntimeError, "ENGINE wasn't initialized."); \
+    } \
+    RTYPEDDATA_DATA(obj) = (engine); \
+} while(0)
+#define GetEngine(obj, engine) do { \
+    TypedData_Get_Struct((obj), ENGINE, &ossl_engine_type, (engine)); \
+    if (!(engine)) { \
+        ossl_raise(rb_eRuntimeError, "ENGINE wasn't initialized."); \
+    } \
+} while (0)
+#define SafeGetEngine(obj, engine) do { \
+    OSSL_Check_Kind((obj), cEngine); \
+    GetPKCS7((obj), (engine)); \
+} while (0)
+
+/*
+ * Classes
+ */
+/* Document-class: OpenSSL::Engine
+ *
+ * This class is the access to openssl's ENGINE cryptographic module
+ * implementation.
+ *
+ * See also, https://www.openssl.org/docs/crypto/engine.html
+ */
+VALUE cEngine;
+/* Document-class: OpenSSL::Engine::EngineError
+ *
+ * This is the generic exception for OpenSSL::Engine related errors
+ */
+VALUE eEngineError;
+
+/*
+ * Private
+ */
+#define OSSL_ENGINE_LOAD_IF_MATCH(x) \
+do{\
+  if(!strcmp(#x, RSTRING_PTR(name))){\
+    ENGINE_load_##x();\
+    return Qtrue;\
+  }\
+}while(0)
+
+static void
+ossl_engine_free(void *engine)
+{
+    ENGINE_free(engine);
+}
+
+static const rb_data_type_t ossl_engine_type = {
+    "OpenSSL/Engine",
+    {
+	0, ossl_engine_free,
+    },
+    0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
+};
+
+/* Document-method: OpenSSL::Engine.load
+ *
+ * call-seq:
+ *   load(enginename = nil)
+ *
+ * This method loads engines. If +name+ is nil, then all builtin engines are
+ * loaded. Otherwise, the given +name+, as a string,  is loaded if available to
+ * your runtime, and returns true. If +name+ is not found, then nil is
+ * returned.
+ *
+ */
+static VALUE
+ossl_engine_s_load(int argc, VALUE *argv, VALUE klass)
+{
+#if !defined(HAVE_ENGINE_LOAD_BUILTIN_ENGINES)
+    return Qnil;
+#else
+    VALUE name;
+
+    rb_scan_args(argc, argv, "01", &name);
+    if(NIL_P(name)){
+        ENGINE_load_builtin_engines();
+        return Qtrue;
+    }
+    StringValueCStr(name);
+#ifndef OPENSSL_NO_STATIC_ENGINE
+#if HAVE_ENGINE_LOAD_DYNAMIC
+    OSSL_ENGINE_LOAD_IF_MATCH(dynamic);
+#endif
+#if HAVE_ENGINE_LOAD_4758CCA
+    OSSL_ENGINE_LOAD_IF_MATCH(4758cca);
+#endif
+#if HAVE_ENGINE_LOAD_AEP
+    OSSL_ENGINE_LOAD_IF_MATCH(aep);
+#endif
+#if HAVE_ENGINE_LOAD_ATALLA
+    OSSL_ENGINE_LOAD_IF_MATCH(atalla);
+#endif
+#if HAVE_ENGINE_LOAD_CHIL
+    OSSL_ENGINE_LOAD_IF_MATCH(chil);
+#endif
+#if HAVE_ENGINE_LOAD_CSWIFT
+    OSSL_ENGINE_LOAD_IF_MATCH(cswift);
+#endif
+#if HAVE_ENGINE_LOAD_NURON
+    OSSL_ENGINE_LOAD_IF_MATCH(nuron);
+#endif
+#if HAVE_ENGINE_LOAD_SUREWARE
+    OSSL_ENGINE_LOAD_IF_MATCH(sureware);
+#endif
+#if HAVE_ENGINE_LOAD_UBSEC
+    OSSL_ENGINE_LOAD_IF_MATCH(ubsec);
+#endif
+#if HAVE_ENGINE_LOAD_PADLOCK
+    OSSL_ENGINE_LOAD_IF_MATCH(padlock);
+#endif
+#if HAVE_ENGINE_LOAD_CAPI
+    OSSL_ENGINE_LOAD_IF_MATCH(capi);
+#endif
+#if HAVE_ENGINE_LOAD_GMP
+    OSSL_ENGINE_LOAD_IF_MATCH(gmp);
+#endif
+#if HAVE_ENGINE_LOAD_GOST
+    OSSL_ENGINE_LOAD_IF_MATCH(gost);
+#endif
+#if HAVE_ENGINE_LOAD_CRYPTODEV
+    OSSL_ENGINE_LOAD_IF_MATCH(cryptodev);
+#endif
+#if HAVE_ENGINE_LOAD_AESNI
+    OSSL_ENGINE_LOAD_IF_MATCH(aesni);
+#endif
+#endif
+#ifdef HAVE_ENGINE_LOAD_OPENBSD_DEV_CRYPTO
+    OSSL_ENGINE_LOAD_IF_MATCH(openbsd_dev_crypto);
+#endif
+    OSSL_ENGINE_LOAD_IF_MATCH(openssl);
+    rb_warning("no such builtin loader for `%"PRIsVALUE"'", name);
+    return Qnil;
+#endif /* HAVE_ENGINE_LOAD_BUILTIN_ENGINES */
+}
+
+/* Document-method: OpenSSL::Engine.cleanup
+ * call-seq:
+ *  OpenSSL::Engine.cleanup
+ *
+ * It is only necessary to run cleanup when engines are loaded via
+ * OpenSSL::Engine.load. However, running cleanup before exit is recommended.
+ *
+ * Note that this is needed and works only in OpenSSL < 1.1.0.
+ */
+static VALUE
+ossl_engine_s_cleanup(VALUE self)
+{
+    ENGINE_cleanup();
+    return Qnil;
+}
+
+/* Document-method: OpenSSL::Engine.engines
+ *
+ * Returns an array of currently loaded engines.
+ */
+static VALUE
+ossl_engine_s_engines(VALUE klass)
+{
+    ENGINE *e;
+    VALUE ary, obj;
+
+    ary = rb_ary_new();
+    for(e = ENGINE_get_first(); e; e = ENGINE_get_next(e)){
+	obj = NewEngine(klass);
+	/* Need a ref count of two here because of ENGINE_free being
+	 * called internally by OpenSSL when moving to the next ENGINE
+	 * and by us when releasing the ENGINE reference */
+	ENGINE_up_ref(e);
+	SetEngine(obj, e);
+        rb_ary_push(ary, obj);
+    }
+
+    return ary;
+}
+
+/* Document-method: OpenSSL::Engine.by_id
+ *
+ * call-seq:
+ *   by_id(name) -> engine
+ *
+ * Fetch the engine as specified by the +id+ String
+ *
+ *   OpenSSL::Engine.by_id("openssl")
+ *    => #<OpenSSL::Engine id="openssl" name="Software engine support">
+ *
+ * See OpenSSL::Engine.engines for the currently loaded engines
+ */
+static VALUE
+ossl_engine_s_by_id(VALUE klass, VALUE id)
+{
+    ENGINE *e;
+    VALUE obj;
+
+    StringValueCStr(id);
+    ossl_engine_s_load(1, &id, klass);
+    obj = NewEngine(klass);
+    if(!(e = ENGINE_by_id(RSTRING_PTR(id))))
+	ossl_raise(eEngineError, NULL);
+    SetEngine(obj, e);
+    if(rb_block_given_p()) rb_yield(obj);
+    if(!ENGINE_init(e))
+	ossl_raise(eEngineError, NULL);
+    ENGINE_ctrl(e, ENGINE_CTRL_SET_PASSWORD_CALLBACK,
+		0, NULL, (void(*)(void))ossl_pem_passwd_cb);
+    ossl_clear_error();
+
+    return obj;
+}
+
+/* Document-method: OpenSSL::Engine#id
+ *
+ * Get the id for this engine
+ *
+ *    OpenSSL::Engine.load
+ *    OpenSSL::Engine.engines #=> [#<OpenSSL::Engine#>, ...]
+ *    OpenSSL::Engine.engines.first.id
+ *	#=> "rsax"
+ */
+static VALUE
+ossl_engine_get_id(VALUE self)
+{
+    ENGINE *e;
+    GetEngine(self, e);
+    return rb_str_new2(ENGINE_get_id(e));
+}
+
+/* Document-method: OpenSSL::Engine#name
+ *
+ * Get the descriptive name for this engine
+ *
+ *    OpenSSL::Engine.load
+ *    OpenSSL::Engine.engines #=> [#<OpenSSL::Engine#>, ...]
+ *    OpenSSL::Engine.engines.first.name
+ *	#=> "RSAX engine support"
+ *
+ */
+static VALUE
+ossl_engine_get_name(VALUE self)
+{
+    ENGINE *e;
+    GetEngine(self, e);
+    return rb_str_new2(ENGINE_get_name(e));
+}
+
+/* Document-method: OpenSSL::Engine#finish
+ *
+ * Releases all internal structural references for this engine.
+ *
+ * May raise an EngineError if the engine is unavailable
+ */
+static VALUE
+ossl_engine_finish(VALUE self)
+{
+    ENGINE *e;
+
+    GetEngine(self, e);
+    if(!ENGINE_finish(e)) ossl_raise(eEngineError, NULL);
+
+    return Qnil;
+}
+
+/* Document-method: OpenSSL::Engine#cipher
+ *
+ * call-seq:
+ *   engine.cipher(name) -> OpenSSL::Cipher
+ *
+ * This returns an OpenSSL::Cipher by +name+, if it is available in this
+ * engine.
+ *
+ * An EngineError will be raised if the cipher is unavailable.
+ *
+ *    e = OpenSSL::Engine.by_id("openssl")
+ *     => #<OpenSSL::Engine id="openssl" name="Software engine support">
+ *    e.cipher("RC4")
+ *     => #<OpenSSL::Cipher:0x007fc5cacc3048>
+ *
+ */
+static VALUE
+ossl_engine_get_cipher(VALUE self, VALUE name)
+{
+    ENGINE *e;
+    const EVP_CIPHER *ciph, *tmp;
+    int nid;
+
+    tmp = EVP_get_cipherbyname(StringValueCStr(name));
+    if(!tmp) ossl_raise(eEngineError, "no such cipher `%"PRIsVALUE"'", name);
+    nid = EVP_CIPHER_nid(tmp);
+    GetEngine(self, e);
+    ciph = ENGINE_get_cipher(e, nid);
+    if(!ciph) ossl_raise(eEngineError, NULL);
+
+    return ossl_cipher_new(ciph);
+}
+
+/* Document-method: OpenSSL::Engine#digest
+ *
+ * call-seq:
+ *   engine.digest(name) -> OpenSSL::Digest
+ *
+ * This returns an OpenSSL::Digest by +name+.
+ *
+ * Will raise an EngineError if the digest is unavailable.
+ *
+ *    e = OpenSSL::Engine.by_id("openssl")
+ *	#=> #<OpenSSL::Engine id="openssl" name="Software engine support">
+ *    e.digest("SHA1")
+ *	#=> #<OpenSSL::Digest: da39a3ee5e6b4b0d3255bfef95601890afd80709>
+ *    e.digest("zomg")
+ *	#=> OpenSSL::Engine::EngineError: no such digest `zomg'
+ */
+static VALUE
+ossl_engine_get_digest(VALUE self, VALUE name)
+{
+    ENGINE *e;
+    const EVP_MD *md, *tmp;
+    int nid;
+
+    tmp = EVP_get_digestbyname(StringValueCStr(name));
+    if(!tmp) ossl_raise(eEngineError, "no such digest `%"PRIsVALUE"'", name);
+    nid = EVP_MD_nid(tmp);
+    GetEngine(self, e);
+    md = ENGINE_get_digest(e, nid);
+    if(!md) ossl_raise(eEngineError, NULL);
+
+    return ossl_digest_new(md);
+}
+
+/* Document-method: OpenSSL::Engine#load_private_key
+ *
+ * call-seq:
+ *    engine.load_private_key(id = nil, data = nil) -> OpenSSL::PKey
+ *
+ * Loads the given private key by +id+ and +data+.
+ *
+ * An EngineError is raised of the OpenSSL::PKey is unavailable.
+ *
+ */
+static VALUE
+ossl_engine_load_privkey(int argc, VALUE *argv, VALUE self)
+{
+    ENGINE *e;
+    EVP_PKEY *pkey;
+    VALUE id, data, obj;
+    char *sid, *sdata;
+
+    rb_scan_args(argc, argv, "02", &id, &data);
+    sid = NIL_P(id) ? NULL : StringValueCStr(id);
+    sdata = NIL_P(data) ? NULL : StringValueCStr(data);
+    GetEngine(self, e);
+    pkey = ENGINE_load_private_key(e, sid, NULL, sdata);
+    if (!pkey) ossl_raise(eEngineError, NULL);
+    obj = ossl_pkey_new(pkey);
+    OSSL_PKEY_SET_PRIVATE(obj);
+
+    return obj;
+}
+
+/* Document-method: OpenSSL::Engine#load_public_key
+ *
+ * call-seq:
+ *    engine.load_public_key(id = nil, data = nil) -> OpenSSL::PKey
+ *
+ * Loads the given private key by +id+ and +data+.
+ *
+ * An EngineError is raised of the OpenSSL::PKey is unavailable.
+ *
+ */
+static VALUE
+ossl_engine_load_pubkey(int argc, VALUE *argv, VALUE self)
+{
+    ENGINE *e;
+    EVP_PKEY *pkey;
+    VALUE id, data;
+    char *sid, *sdata;
+
+    rb_scan_args(argc, argv, "02", &id, &data);
+    sid = NIL_P(id) ? NULL : StringValueCStr(id);
+    sdata = NIL_P(data) ? NULL : StringValueCStr(data);
+    GetEngine(self, e);
+    pkey = ENGINE_load_public_key(e, sid, NULL, sdata);
+    if (!pkey) ossl_raise(eEngineError, NULL);
+
+    return ossl_pkey_new(pkey);
+}
+
+/* Document-method: OpenSSL::Engine#set_default
+ *
+ * call-seq:
+ *    engine.set_default(flag)
+ *
+ * Set the defaults for this engine with the given +flag+.
+ *
+ * These flags are used to control combinations of algorithm methods.
+ *
+ * +flag+ can be one of the following, other flags are available depending on
+ * your OS.
+ *
+ * [All flags]  0xFFFF
+ * [No flags]	0x0000
+ *
+ * See also <openssl/engine.h>
+ */
+static VALUE
+ossl_engine_set_default(VALUE self, VALUE flag)
+{
+    ENGINE *e;
+    int f = NUM2INT(flag);
+
+    GetEngine(self, e);
+    ENGINE_set_default(e, f);
+
+    return Qtrue;
+}
+
+/* Document-method: OpenSSL::Engine#ctrl_cmd
+ *
+ * call-seq:
+ *    engine.ctrl_cmd(command, value = nil) -> engine
+ *
+ * Send the given +command+ to this engine.
+ *
+ * Raises an EngineError if the +command+ fails.
+ */
+static VALUE
+ossl_engine_ctrl_cmd(int argc, VALUE *argv, VALUE self)
+{
+    ENGINE *e;
+    VALUE cmd, val;
+    int ret;
+
+    GetEngine(self, e);
+    rb_scan_args(argc, argv, "11", &cmd, &val);
+    ret = ENGINE_ctrl_cmd_string(e, StringValueCStr(cmd),
+				 NIL_P(val) ? NULL : StringValueCStr(val), 0);
+    if (!ret) ossl_raise(eEngineError, NULL);
+
+    return self;
+}
+
+static VALUE
+ossl_engine_cmd_flag_to_name(int flag)
+{
+    switch(flag){
+    case ENGINE_CMD_FLAG_NUMERIC:  return rb_str_new2("NUMERIC");
+    case ENGINE_CMD_FLAG_STRING:   return rb_str_new2("STRING");
+    case ENGINE_CMD_FLAG_NO_INPUT: return rb_str_new2("NO_INPUT");
+    case ENGINE_CMD_FLAG_INTERNAL: return rb_str_new2("INTERNAL");
+    default: return rb_str_new2("UNKNOWN");
+    }
+}
+
+/* Document-method: OpenSSL::Engine#cmds
+ *
+ * Returns an array of command definitions for the current engine
+ */
+static VALUE
+ossl_engine_get_cmds(VALUE self)
+{
+    ENGINE *e;
+    const ENGINE_CMD_DEFN *defn, *p;
+    VALUE ary, tmp;
+
+    GetEngine(self, e);
+    ary = rb_ary_new();
+    if ((defn = ENGINE_get_cmd_defns(e)) != NULL){
+	for (p = defn; p->cmd_num > 0; p++){
+	    tmp = rb_ary_new();
+	    rb_ary_push(tmp, rb_str_new2(p->cmd_name));
+	    rb_ary_push(tmp, rb_str_new2(p->cmd_desc));
+	    rb_ary_push(tmp, ossl_engine_cmd_flag_to_name(p->cmd_flags));
+	    rb_ary_push(ary, tmp);
+	}
+    }
+
+    return ary;
+}
+
+/* Document-method: OpenSSL::Engine#inspect
+ *
+ * Pretty print this engine
+ */
+static VALUE
+ossl_engine_inspect(VALUE self)
+{
+    ENGINE *e;
+
+    GetEngine(self, e);
+    return rb_sprintf("#<%"PRIsVALUE" id=\"%s\" name=\"%s\">",
+		      rb_obj_class(self), ENGINE_get_id(e), ENGINE_get_name(e));
+}
+
+#define DefEngineConst(x) rb_define_const(cEngine, #x, INT2NUM(ENGINE_##x))
+
+void
+Init_ossl_engine(void)
+{
+#if 0
+    mOSSL = rb_define_module("OpenSSL");
+    eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError);
+#endif
+
+    cEngine = rb_define_class_under(mOSSL, "Engine", rb_cObject);
+    eEngineError = rb_define_class_under(cEngine, "EngineError", eOSSLError);
+
+    rb_undef_alloc_func(cEngine);
+    rb_define_singleton_method(cEngine, "load", ossl_engine_s_load, -1);
+    rb_define_singleton_method(cEngine, "cleanup", ossl_engine_s_cleanup, 0);
+    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_define_method(cEngine, "id", ossl_engine_get_id, 0);
+    rb_define_method(cEngine, "name", ossl_engine_get_name, 0);
+    rb_define_method(cEngine, "finish", ossl_engine_finish, 0);
+    rb_define_method(cEngine, "cipher", ossl_engine_get_cipher, 1);
+    rb_define_method(cEngine, "digest",  ossl_engine_get_digest, 1);
+    rb_define_method(cEngine, "load_private_key", ossl_engine_load_privkey, -1);
+    rb_define_method(cEngine, "load_public_key", ossl_engine_load_pubkey, -1);
+    rb_define_method(cEngine, "set_default", ossl_engine_set_default, 1);
+    rb_define_method(cEngine, "ctrl_cmd", ossl_engine_ctrl_cmd, -1);
+    rb_define_method(cEngine, "cmds", ossl_engine_get_cmds, 0);
+    rb_define_method(cEngine, "inspect", ossl_engine_inspect, 0);
+
+    DefEngineConst(METHOD_RSA);
+    DefEngineConst(METHOD_DSA);
+    DefEngineConst(METHOD_DH);
+    DefEngineConst(METHOD_RAND);
+#ifdef ENGINE_METHOD_BN_MOD_EXP
+    DefEngineConst(METHOD_BN_MOD_EXP);
+#endif
+#ifdef ENGINE_METHOD_BN_MOD_EXP_CRT
+    DefEngineConst(METHOD_BN_MOD_EXP_CRT);
+#endif
+    DefEngineConst(METHOD_CIPHERS);
+    DefEngineConst(METHOD_DIGESTS);
+    DefEngineConst(METHOD_ALL);
+    DefEngineConst(METHOD_NONE);
+}
+#else
+void
+Init_ossl_engine(void)
+{
+}
+#endif
diff --git a/ext/openssl/ossl_engine.h b/ext/openssl/ossl_engine.h
new file mode 100644
index 0000000..cd548be
--- /dev/null
+++ b/ext/openssl/ossl_engine.h
@@ -0,0 +1,19 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2003  Michal Rokos <m.rokos at sh.cvut.cz>
+ * Copyright (C) 2003  GOTOU Yuuzou <gotoyuzo at notwork.org>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#if !defined(OSSL_ENGINE_H)
+#define OSSL_ENGINE_H
+
+extern VALUE cEngine;
+extern VALUE eEngineError;
+
+void Init_ossl_engine(void);
+
+#endif /* OSSL_ENGINE_H */
diff --git a/ext/openssl/ossl_hmac.c b/ext/openssl/ossl_hmac.c
new file mode 100644
index 0000000..270979e
--- /dev/null
+++ b/ext/openssl/ossl_hmac.c
@@ -0,0 +1,398 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2001-2002  Michal Rokos <m.rokos at sh.cvut.cz>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#if !defined(OPENSSL_NO_HMAC)
+
+#include "ossl.h"
+
+#define NewHMAC(klass) \
+    TypedData_Wrap_Struct((klass), &ossl_hmac_type, 0)
+#define GetHMAC(obj, ctx) do { \
+    TypedData_Get_Struct((obj), HMAC_CTX, &ossl_hmac_type, (ctx)); \
+    if (!(ctx)) { \
+	ossl_raise(rb_eRuntimeError, "HMAC wasn't initialized"); \
+    } \
+} while (0)
+#define SafeGetHMAC(obj, ctx) do { \
+    OSSL_Check_Kind((obj), cHMAC); \
+    GetHMAC((obj), (ctx)); \
+} while (0)
+
+/*
+ * Classes
+ */
+VALUE cHMAC;
+VALUE eHMACError;
+
+/*
+ * Public
+ */
+
+/*
+ * Private
+ */
+static void
+ossl_hmac_free(void *ctx)
+{
+    HMAC_CTX_free(ctx);
+}
+
+static const rb_data_type_t ossl_hmac_type = {
+    "OpenSSL/HMAC",
+    {
+	0, ossl_hmac_free,
+    },
+    0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
+};
+
+static VALUE
+ossl_hmac_alloc(VALUE klass)
+{
+    VALUE obj;
+    HMAC_CTX *ctx;
+
+    obj = NewHMAC(klass);
+    ctx = HMAC_CTX_new();
+    if (!ctx)
+	ossl_raise(eHMACError, NULL);
+    RTYPEDDATA_DATA(obj) = ctx;
+
+    return obj;
+}
+
+
+/*
+ *  call-seq:
+ *     HMAC.new(key, digest) -> hmac
+ *
+ * Returns an instance of OpenSSL::HMAC set with the key and digest
+ * algorithm to be used. The instance represents the initial state of
+ * the message authentication code before any data has been processed.
+ * To process data with it, use the instance method #update with your
+ * data as an argument.
+ *
+ * === Example
+ *
+ *	key = 'key'
+ * 	digest = OpenSSL::Digest.new('sha1')
+ * 	instance = OpenSSL::HMAC.new(key, digest)
+ * 	#=> f42bb0eeb018ebbd4597ae7213711ec60760843f
+ * 	instance.class
+ * 	#=> OpenSSL::HMAC
+ *
+ * === A note about comparisons
+ *
+ * Two instances won't be equal when they're compared, even if they have the
+ * same value. Use #to_s or #hexdigest to return the authentication code that
+ * the instance represents. For example:
+ *
+ *	other_instance = OpenSSL::HMAC.new('key', OpenSSL::Digest.new('sha1'))
+ *  	#=> f42bb0eeb018ebbd4597ae7213711ec60760843f
+ *  	instance
+ *  	#=> f42bb0eeb018ebbd4597ae7213711ec60760843f
+ *  	instance == other_instance
+ *  	#=> false
+ *  	instance.to_s == other_instance.to_s
+ *  	#=> true
+ *
+ */
+static VALUE
+ossl_hmac_initialize(VALUE self, VALUE key, VALUE digest)
+{
+    HMAC_CTX *ctx;
+
+    StringValue(key);
+    GetHMAC(self, ctx);
+    HMAC_Init_ex(ctx, RSTRING_PTR(key), RSTRING_LENINT(key),
+		 GetDigestPtr(digest), NULL);
+
+    return self;
+}
+
+static VALUE
+ossl_hmac_copy(VALUE self, VALUE other)
+{
+    HMAC_CTX *ctx1, *ctx2;
+
+    rb_check_frozen(self);
+    if (self == other) return self;
+
+    GetHMAC(self, ctx1);
+    SafeGetHMAC(other, ctx2);
+
+    if (!HMAC_CTX_copy(ctx1, ctx2))
+	ossl_raise(eHMACError, "HMAC_CTX_copy");
+    return self;
+}
+
+/*
+ *  call-seq:
+ *     hmac.update(string) -> self
+ *
+ * Returns +self+ updated with the message to be authenticated.
+ * Can be called repeatedly with chunks of the message.
+ *
+ * === Example
+ *
+ *	first_chunk = 'The quick brown fox jumps '
+ * 	second_chunk = 'over the lazy dog'
+ *
+ * 	instance.update(first_chunk)
+ * 	#=> 5b9a8038a65d571076d97fe783989e52278a492a
+ * 	instance.update(second_chunk)
+ * 	#=> de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9
+ *
+ */
+static VALUE
+ossl_hmac_update(VALUE self, VALUE data)
+{
+    HMAC_CTX *ctx;
+
+    StringValue(data);
+    GetHMAC(self, ctx);
+    HMAC_Update(ctx, (unsigned char *)RSTRING_PTR(data), RSTRING_LEN(data));
+
+    return self;
+}
+
+static void
+hmac_final(HMAC_CTX *ctx, unsigned char *buf, unsigned int *buf_len)
+{
+    HMAC_CTX *final;
+
+    final = HMAC_CTX_new();
+    if (!final)
+	ossl_raise(eHMACError, "HMAC_CTX_new");
+
+    if (!HMAC_CTX_copy(final, ctx)) {
+	HMAC_CTX_free(final);
+	ossl_raise(eHMACError, "HMAC_CTX_copy");
+    }
+
+    HMAC_Final(final, buf, buf_len);
+    HMAC_CTX_free(final);
+}
+
+/*
+ *  call-seq:
+ *     hmac.digest -> string
+ *
+ * Returns the authentication code an instance represents as a binary string.
+ *
+ * === Example
+ *  instance = OpenSSL::HMAC.new('key', OpenSSL::Digest.new('sha1'))
+ *  #=> f42bb0eeb018ebbd4597ae7213711ec60760843f
+ *  instance.digest
+ *  #=> "\xF4+\xB0\xEE\xB0\x18\xEB\xBDE\x97\xAEr\x13q\x1E\xC6\a`\x84?"
+ */
+static VALUE
+ossl_hmac_digest(VALUE self)
+{
+    HMAC_CTX *ctx;
+    unsigned int buf_len;
+    VALUE ret;
+
+    GetHMAC(self, ctx);
+    ret = rb_str_new(NULL, EVP_MAX_MD_SIZE);
+    hmac_final(ctx, (unsigned char *)RSTRING_PTR(ret), &buf_len);
+    assert(buf_len <= EVP_MAX_MD_SIZE);
+    rb_str_set_len(ret, buf_len);
+
+    return ret;
+}
+
+/*
+ *  call-seq:
+ *     hmac.hexdigest -> string
+ *
+ * Returns the authentication code an instance represents as a hex-encoded
+ * string.
+ */
+static VALUE
+ossl_hmac_hexdigest(VALUE self)
+{
+    HMAC_CTX *ctx;
+    unsigned char buf[EVP_MAX_MD_SIZE];
+    unsigned int buf_len;
+    VALUE ret;
+
+    GetHMAC(self, ctx);
+    hmac_final(ctx, buf, &buf_len);
+    ret = rb_str_new(NULL, buf_len * 2);
+    ossl_bin2hex(buf, RSTRING_PTR(ret), buf_len);
+
+    return ret;
+}
+
+/*
+ *  call-seq:
+ *     hmac.reset -> self
+ *
+ * Returns +self+ as it was when it was first initialized, with all processed
+ * data cleared from it.
+ *
+ * === Example
+ *
+ *	data = "The quick brown fox jumps over the lazy dog"
+ * 	instance = OpenSSL::HMAC.new('key', OpenSSL::Digest.new('sha1'))
+ * 	#=> f42bb0eeb018ebbd4597ae7213711ec60760843f
+ *
+ * 	instance.update(data)
+ * 	#=> de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9
+ * 	instance.reset
+ * 	#=> f42bb0eeb018ebbd4597ae7213711ec60760843f
+ *
+ */
+static VALUE
+ossl_hmac_reset(VALUE self)
+{
+    HMAC_CTX *ctx;
+
+    GetHMAC(self, ctx);
+    HMAC_Init_ex(ctx, NULL, 0, NULL, NULL);
+
+    return self;
+}
+
+/*
+ *  call-seq:
+ *     HMAC.digest(digest, key, data) -> aString
+ *
+ * Returns the authentication code as a binary string. The +digest+ parameter
+ * must be an instance of OpenSSL::Digest.
+ *
+ * === Example
+ *
+ *	key = 'key'
+ * 	data = 'The quick brown fox jumps over the lazy dog'
+ * 	digest = OpenSSL::Digest.new('sha1')
+ *
+ * 	hmac = OpenSSL::HMAC.digest(digest, key, data)
+ * 	#=> "\xDE|\x9B\x85\xB8\xB7\x8A\xA6\xBC\x8Az6\xF7\n\x90p\x1C\x9D\xB4\xD9"
+ *
+ */
+static VALUE
+ossl_hmac_s_digest(VALUE klass, VALUE digest, VALUE key, VALUE data)
+{
+    unsigned char *buf;
+    unsigned int buf_len;
+
+    StringValue(key);
+    StringValue(data);
+    buf = HMAC(GetDigestPtr(digest), RSTRING_PTR(key), RSTRING_LENINT(key),
+	       (unsigned char *)RSTRING_PTR(data), RSTRING_LEN(data), NULL, &buf_len);
+
+    return rb_str_new((const char *)buf, buf_len);
+}
+
+/*
+ *  call-seq:
+ *     HMAC.hexdigest(digest, key, data) -> aString
+ *
+ * Returns the authentication code as a hex-encoded string. The +digest+
+ * parameter must be an instance of OpenSSL::Digest.
+ *
+ * === Example
+ *
+ *	key = 'key'
+ * 	data = 'The quick brown fox jumps over the lazy dog'
+ * 	digest = OpenSSL::Digest.new('sha1')
+ *
+ * 	hmac = OpenSSL::HMAC.hexdigest(digest, key, data)
+ * 	#=> "de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9"
+ *
+ */
+static VALUE
+ossl_hmac_s_hexdigest(VALUE klass, VALUE digest, VALUE key, VALUE data)
+{
+    unsigned char buf[EVP_MAX_MD_SIZE];
+    unsigned int buf_len;
+    VALUE ret;
+
+    StringValue(key);
+    StringValue(data);
+
+    if (!HMAC(GetDigestPtr(digest), RSTRING_PTR(key), RSTRING_LENINT(key),
+	      (unsigned char *)RSTRING_PTR(data), RSTRING_LEN(data),
+	      buf, &buf_len))
+	ossl_raise(eHMACError, "HMAC");
+
+    ret = rb_str_new(NULL, buf_len * 2);
+    ossl_bin2hex(buf, RSTRING_PTR(ret), buf_len);
+
+    return ret;
+}
+
+/*
+ * INIT
+ */
+void
+Init_ossl_hmac(void)
+{
+#if 0
+    mOSSL = rb_define_module("OpenSSL");
+    eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError);
+#endif
+
+    /*
+     * Document-class: OpenSSL::HMAC
+     *
+     * OpenSSL::HMAC allows computing Hash-based Message Authentication Code
+     * (HMAC). It is a type of message authentication code (MAC) involving a
+     * hash function in combination with a key. HMAC can be used to verify the
+     * integrity of a message as well as the authenticity.
+     *
+     * OpenSSL::HMAC has a similar interface to OpenSSL::Digest.
+     *
+     * === HMAC-SHA256 using one-shot interface
+     *
+     *   key = "key"
+     *   data = "message-to-be-authenticated"
+     *   mac = OpenSSL::HMAC.hexdigest("SHA256", key, data)
+     *   #=> "cddb0db23f469c8bf072b21fd837149bd6ace9ab771cceef14c9e517cc93282e"
+     *
+     * === HMAC-SHA256 using incremental interface
+     *
+     *   data1 = File.read("file1")
+     *   data2 = File.read("file2")
+     *   key = "key"
+     *   digest = OpenSSL::Digest::SHA256.new
+     *   hmac = OpenSSL::HMAC.new(key, digest)
+     *   hmac << data1
+     *   hmac << data2
+     *   mac = hmac.digest
+     */
+    eHMACError = rb_define_class_under(mOSSL, "HMACError", eOSSLError);
+
+    cHMAC = rb_define_class_under(mOSSL, "HMAC", rb_cObject);
+
+    rb_define_alloc_func(cHMAC, ossl_hmac_alloc);
+    rb_define_singleton_method(cHMAC, "digest", ossl_hmac_s_digest, 3);
+    rb_define_singleton_method(cHMAC, "hexdigest", ossl_hmac_s_hexdigest, 3);
+
+    rb_define_method(cHMAC, "initialize", ossl_hmac_initialize, 2);
+    rb_define_copy_func(cHMAC, ossl_hmac_copy);
+
+    rb_define_method(cHMAC, "reset", ossl_hmac_reset, 0);
+    rb_define_method(cHMAC, "update", ossl_hmac_update, 1);
+    rb_define_alias(cHMAC, "<<", "update");
+    rb_define_method(cHMAC, "digest", ossl_hmac_digest, 0);
+    rb_define_method(cHMAC, "hexdigest", ossl_hmac_hexdigest, 0);
+    rb_define_alias(cHMAC, "inspect", "hexdigest");
+    rb_define_alias(cHMAC, "to_s", "hexdigest");
+}
+
+#else /* NO_HMAC */
+#  warning >>> OpenSSL is compiled without HMAC support <<<
+void
+Init_ossl_hmac(void)
+{
+    rb_warning("HMAC is not available: OpenSSL is compiled without HMAC.");
+}
+#endif /* NO_HMAC */
diff --git a/ext/openssl/ossl_hmac.h b/ext/openssl/ossl_hmac.h
new file mode 100644
index 0000000..7c51f47
--- /dev/null
+++ b/ext/openssl/ossl_hmac.h
@@ -0,0 +1,18 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2001-2002  Michal Rokos <m.rokos at sh.cvut.cz>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#if !defined(_OSSL_HMAC_H_)
+#define _OSSL_HMAC_H_
+
+extern VALUE cHMAC;
+extern VALUE eHMACError;
+
+void Init_ossl_hmac(void);
+
+#endif /* _OSSL_HMAC_H_ */
diff --git a/ext/openssl/ossl_ns_spki.c b/ext/openssl/ossl_ns_spki.c
new file mode 100644
index 0000000..4d978bd
--- /dev/null
+++ b/ext/openssl/ossl_ns_spki.c
@@ -0,0 +1,401 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2001-2002  Michal Rokos <m.rokos at sh.cvut.cz>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#include "ossl.h"
+
+#define NewSPKI(klass) \
+    TypedData_Wrap_Struct((klass), &ossl_netscape_spki_type, 0)
+#define SetSPKI(obj, spki) do { \
+    if (!(spki)) { \
+	ossl_raise(rb_eRuntimeError, "SPKI wasn't initialized!"); \
+    } \
+    RTYPEDDATA_DATA(obj) = (spki); \
+} while (0)
+#define GetSPKI(obj, spki) do { \
+    TypedData_Get_Struct((obj), NETSCAPE_SPKI, &ossl_netscape_spki_type, (spki)); \
+    if (!(spki)) { \
+	ossl_raise(rb_eRuntimeError, "SPKI wasn't initialized!"); \
+    } \
+} while (0)
+
+/*
+ * Classes
+ */
+VALUE mNetscape;
+VALUE cSPKI;
+VALUE eSPKIError;
+
+/*
+ * Public functions
+ */
+
+/*
+ * Private functions
+ */
+
+static void
+ossl_netscape_spki_free(void *spki)
+{
+    NETSCAPE_SPKI_free(spki);
+}
+
+static const rb_data_type_t ossl_netscape_spki_type = {
+    "OpenSSL/NETSCAPE_SPKI",
+    {
+	0, ossl_netscape_spki_free,
+    },
+    0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
+};
+
+static VALUE
+ossl_spki_alloc(VALUE klass)
+{
+    NETSCAPE_SPKI *spki;
+    VALUE obj;
+
+    obj = NewSPKI(klass);
+    if (!(spki = NETSCAPE_SPKI_new())) {
+	ossl_raise(eSPKIError, NULL);
+    }
+    SetSPKI(obj, spki);
+
+    return obj;
+}
+
+/*
+ * call-seq:
+ *    SPKI.new([request]) => spki
+ *
+ * === Parameters
+ * * +request+ - optional raw request, either in PEM or DER format.
+ */
+static VALUE
+ossl_spki_initialize(int argc, VALUE *argv, VALUE self)
+{
+    NETSCAPE_SPKI *spki;
+    VALUE buffer;
+    const unsigned char *p;
+
+    if (rb_scan_args(argc, argv, "01", &buffer) == 0) {
+	return self;
+    }
+    StringValue(buffer);
+    if (!(spki = NETSCAPE_SPKI_b64_decode(RSTRING_PTR(buffer), RSTRING_LENINT(buffer)))) {
+	ossl_clear_error();
+	p = (unsigned char *)RSTRING_PTR(buffer);
+	if (!(spki = d2i_NETSCAPE_SPKI(NULL, &p, RSTRING_LEN(buffer)))) {
+	    ossl_raise(eSPKIError, NULL);
+	}
+    }
+    NETSCAPE_SPKI_free(DATA_PTR(self));
+    SetSPKI(self, spki);
+
+    return self;
+}
+
+/*
+ * call-seq:
+ *    spki.to_der => DER-encoded string
+ *
+ * Returns the DER encoding of this SPKI.
+ */
+static VALUE
+ossl_spki_to_der(VALUE self)
+{
+    NETSCAPE_SPKI *spki;
+    VALUE str;
+    long len;
+    unsigned char *p;
+
+    GetSPKI(self, spki);
+    if ((len = i2d_NETSCAPE_SPKI(spki, NULL)) <= 0)
+        ossl_raise(eX509CertError, NULL);
+    str = rb_str_new(0, len);
+    p = (unsigned char *)RSTRING_PTR(str);
+    if (i2d_NETSCAPE_SPKI(spki, &p) <= 0)
+        ossl_raise(eX509CertError, NULL);
+    ossl_str_adjust(str, p);
+
+    return str;
+}
+
+/*
+ * call-seq:
+ *    spki.to_pem => PEM-encoded string
+ *
+ * Returns the PEM encoding of this SPKI.
+ */
+static VALUE
+ossl_spki_to_pem(VALUE self)
+{
+    NETSCAPE_SPKI *spki;
+    char *data;
+    VALUE str;
+
+    GetSPKI(self, spki);
+    if (!(data = NETSCAPE_SPKI_b64_encode(spki))) {
+	ossl_raise(eSPKIError, NULL);
+    }
+    str = ossl_buf2str(data, rb_long2int(strlen(data)));
+
+    return str;
+}
+
+/*
+ * call-seq:
+ *    spki.to_text => string
+ *
+ * Returns a textual representation of this SPKI, useful for debugging
+ * purposes.
+ */
+static VALUE
+ossl_spki_print(VALUE self)
+{
+    NETSCAPE_SPKI *spki;
+    BIO *out;
+
+    GetSPKI(self, spki);
+    if (!(out = BIO_new(BIO_s_mem()))) {
+	ossl_raise(eSPKIError, NULL);
+    }
+    if (!NETSCAPE_SPKI_print(out, spki)) {
+	BIO_free(out);
+	ossl_raise(eSPKIError, NULL);
+    }
+
+    return ossl_membio2str(out);
+}
+
+/*
+ * call-seq:
+ *    spki.public_key => pkey
+ *
+ * Returns the public key associated with the SPKI, an instance of
+ * OpenSSL::PKey.
+ */
+static VALUE
+ossl_spki_get_public_key(VALUE self)
+{
+    NETSCAPE_SPKI *spki;
+    EVP_PKEY *pkey;
+
+    GetSPKI(self, spki);
+    if (!(pkey = NETSCAPE_SPKI_get_pubkey(spki))) { /* adds an reference */
+	ossl_raise(eSPKIError, NULL);
+    }
+
+    return ossl_pkey_new(pkey); /* NO DUP - OK */
+}
+
+/*
+ * call-seq:
+ *    spki.public_key = pub => pkey
+ *
+ * === Parameters
+ * * +pub+ - the public key to be set for this instance
+ *
+ * Sets the public key to be associated with the SPKI, an instance of
+ * OpenSSL::PKey. This should be the public key corresponding to the
+ * private key used for signing the SPKI.
+ */
+static VALUE
+ossl_spki_set_public_key(VALUE self, VALUE key)
+{
+    NETSCAPE_SPKI *spki;
+
+    GetSPKI(self, spki);
+    if (!NETSCAPE_SPKI_set_pubkey(spki, GetPKeyPtr(key))) { /* NO NEED TO DUP */
+	ossl_raise(eSPKIError, NULL);
+    }
+
+    return key;
+}
+
+/*
+ * call-seq:
+ *    spki.challenge => string
+ *
+ * Returns the challenge string associated with this SPKI.
+ */
+static VALUE
+ossl_spki_get_challenge(VALUE self)
+{
+    NETSCAPE_SPKI *spki;
+
+    GetSPKI(self, spki);
+    if (spki->spkac->challenge->length <= 0) {
+	OSSL_Debug("Challenge.length <= 0?");
+	return rb_str_new(0, 0);
+    }
+
+    return rb_str_new((const char *)spki->spkac->challenge->data,
+		      spki->spkac->challenge->length);
+}
+
+/*
+ * call-seq:
+ *    spki.challenge = str => string
+ *
+ * === Parameters
+ * * +str+ - the challenge string to be set for this instance
+ *
+ * Sets the challenge to be associated with the SPKI. May be used by the
+ * server, e.g. to prevent replay.
+ */
+static VALUE
+ossl_spki_set_challenge(VALUE self, VALUE str)
+{
+    NETSCAPE_SPKI *spki;
+
+    StringValue(str);
+    GetSPKI(self, spki);
+    if (!ASN1_STRING_set(spki->spkac->challenge, RSTRING_PTR(str),
+			 RSTRING_LENINT(str))) {
+	ossl_raise(eSPKIError, NULL);
+    }
+
+    return str;
+}
+
+/*
+ * call-seq:
+ *    spki.sign(key, digest) => spki
+ *
+ * === Parameters
+ * * +key+ - the private key to be used for signing this instance
+ * * +digest+ - the digest to be used for signing this instance
+ *
+ * To sign an SPKI, the private key corresponding to the public key set
+ * for this instance should be used, in addition to a digest algorithm in
+ * the form of an OpenSSL::Digest. The private key should be an instance of
+ * OpenSSL::PKey.
+ */
+static VALUE
+ossl_spki_sign(VALUE self, VALUE key, VALUE digest)
+{
+    NETSCAPE_SPKI *spki;
+    EVP_PKEY *pkey;
+    const EVP_MD *md;
+
+    pkey = GetPrivPKeyPtr(key); /* NO NEED TO DUP */
+    md = GetDigestPtr(digest);
+    GetSPKI(self, spki);
+    if (!NETSCAPE_SPKI_sign(spki, pkey, md)) {
+	ossl_raise(eSPKIError, NULL);
+    }
+
+    return self;
+}
+
+/*
+ * call-seq:
+ *    spki.verify(key) => boolean
+ *
+ * === Parameters
+ * * +key+ - the public key to be used for verifying the SPKI signature
+ *
+ * Returns +true+ if the signature is valid, +false+ otherwise. To verify an
+ * SPKI, the public key contained within the SPKI should be used.
+ */
+static VALUE
+ossl_spki_verify(VALUE self, VALUE key)
+{
+    NETSCAPE_SPKI *spki;
+
+    GetSPKI(self, spki);
+    switch (NETSCAPE_SPKI_verify(spki, GetPKeyPtr(key))) { /* NO NEED TO DUP */
+    case 0:
+	return Qfalse;
+    case 1:
+	return Qtrue;
+    default:
+	ossl_raise(eSPKIError, NULL);
+    }
+    return Qnil; /* dummy */
+}
+
+/* Document-class: OpenSSL::Netscape::SPKI
+ *
+ * A Simple Public Key Infrastructure implementation (pronounced "spooky").
+ * The structure is defined as
+ *   PublicKeyAndChallenge ::= SEQUENCE {
+ *     spki SubjectPublicKeyInfo,
+ *     challenge IA5STRING
+ *   }
+ *
+ *   SignedPublicKeyAndChallenge ::= SEQUENCE {
+ *     publicKeyAndChallenge PublicKeyAndChallenge,
+ *     signatureAlgorithm AlgorithmIdentifier,
+ *     signature BIT STRING
+ *   }
+ * where the definitions of SubjectPublicKeyInfo and AlgorithmIdentifier can
+ * be found in RFC5280. SPKI is typically used in browsers for generating
+ * a public/private key pair and a subsequent certificate request, using
+ * the HTML <keygen> element.
+ *
+ * == Examples
+ *
+ * === Creating an SPKI
+ *   key = OpenSSL::PKey::RSA.new 2048
+ *   spki = OpenSSL::Netscape::SPKI.new
+ *   spki.challenge = "RandomChallenge"
+ *   spki.public_key = key.public_key
+ *   spki.sign(key, OpenSSL::Digest::SHA256.new)
+ *   #send a request containing this to a server generating a certificate
+ * === Verifying an SPKI request
+ *   request = #...
+ *   spki = OpenSSL::Netscape::SPKI.new request
+ *   unless spki.verify(spki.public_key)
+ *     # signature is invalid
+ *   end
+ *   #proceed
+ */
+
+/* Document-module: OpenSSL::Netscape
+ *
+ * OpenSSL::Netscape is a namespace for SPKI (Simple Public Key
+ * Infrastructure) which implements Signed Public Key and Challenge.
+ * See {RFC 2692}[http://tools.ietf.org/html/rfc2692] and {RFC
+ * 2693}[http://tools.ietf.org/html/rfc2692] for details.
+ */
+
+/* Document-class: OpenSSL::Netscape::SPKIError
+ *
+ * Generic Exception class that is raised if an error occurs during an
+ * operation on an instance of OpenSSL::Netscape::SPKI.
+ */
+
+void
+Init_ossl_ns_spki(void)
+{
+#if 0
+    mOSSL = rb_define_module("OpenSSL");
+    eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError);
+#endif
+
+    mNetscape = rb_define_module_under(mOSSL, "Netscape");
+
+    eSPKIError = rb_define_class_under(mNetscape, "SPKIError", eOSSLError);
+
+    cSPKI = rb_define_class_under(mNetscape, "SPKI", rb_cObject);
+
+    rb_define_alloc_func(cSPKI, ossl_spki_alloc);
+    rb_define_method(cSPKI, "initialize", ossl_spki_initialize, -1);
+
+    rb_define_method(cSPKI, "to_der", ossl_spki_to_der, 0);
+    rb_define_method(cSPKI, "to_pem", ossl_spki_to_pem, 0);
+    rb_define_alias(cSPKI, "to_s", "to_pem");
+    rb_define_method(cSPKI, "to_text", ossl_spki_print, 0);
+    rb_define_method(cSPKI, "public_key", ossl_spki_get_public_key, 0);
+    rb_define_method(cSPKI, "public_key=", ossl_spki_set_public_key, 1);
+    rb_define_method(cSPKI, "sign", ossl_spki_sign, 2);
+    rb_define_method(cSPKI, "verify", ossl_spki_verify, 1);
+    rb_define_method(cSPKI, "challenge", ossl_spki_get_challenge, 0);
+    rb_define_method(cSPKI, "challenge=", ossl_spki_set_challenge, 1);
+}
diff --git a/ext/openssl/ossl_ns_spki.h b/ext/openssl/ossl_ns_spki.h
new file mode 100644
index 0000000..62ba8cb
--- /dev/null
+++ b/ext/openssl/ossl_ns_spki.h
@@ -0,0 +1,19 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2001-2002  Michal Rokos <m.rokos at sh.cvut.cz>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#if !defined(_OSSL_NS_SPKI_H_)
+#define _OSSL_NS_SPKI_H_
+
+extern VALUE mNetscape;
+extern VALUE cSPKI;
+extern VALUE eSPKIError;
+
+void Init_ossl_ns_spki(void);
+
+#endif /* _OSSL_NS_SPKI_H_ */
diff --git a/ext/openssl/ossl_ocsp.c b/ext/openssl/ossl_ocsp.c
new file mode 100644
index 0000000..a8b3503
--- /dev/null
+++ b/ext/openssl/ossl_ocsp.c
@@ -0,0 +1,2013 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2003  Michal Rokos <m.rokos at sh.cvut.cz>
+ * Copyright (C) 2003  GOTOU Yuuzou <gotoyuzo at notwork.org>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#include "ossl.h"
+
+#if !defined(OPENSSL_NO_OCSP)
+
+#define NewOCSPReq(klass) \
+    TypedData_Wrap_Struct((klass), &ossl_ocsp_request_type, 0)
+#define SetOCSPReq(obj, req) do { \
+    if(!(req)) ossl_raise(rb_eRuntimeError, "Request wasn't initialized!"); \
+    RTYPEDDATA_DATA(obj) = (req); \
+} while (0)
+#define GetOCSPReq(obj, req) do { \
+    TypedData_Get_Struct((obj), OCSP_REQUEST, &ossl_ocsp_request_type, (req)); \
+    if(!(req)) ossl_raise(rb_eRuntimeError, "Request wasn't initialized!"); \
+} while (0)
+#define SafeGetOCSPReq(obj, req) do { \
+    OSSL_Check_Kind((obj), cOCSPReq); \
+    GetOCSPReq((obj), (req)); \
+} while (0)
+
+#define NewOCSPRes(klass) \
+    TypedData_Wrap_Struct((klass), &ossl_ocsp_response_type, 0)
+#define SetOCSPRes(obj, res) do { \
+    if(!(res)) ossl_raise(rb_eRuntimeError, "Response wasn't initialized!"); \
+    RTYPEDDATA_DATA(obj) = (res); \
+} while (0)
+#define GetOCSPRes(obj, res) do { \
+    TypedData_Get_Struct((obj), OCSP_RESPONSE, &ossl_ocsp_response_type, (res)); \
+    if(!(res)) ossl_raise(rb_eRuntimeError, "Response wasn't initialized!"); \
+} while (0)
+#define SafeGetOCSPRes(obj, res) do { \
+    OSSL_Check_Kind((obj), cOCSPRes); \
+    GetOCSPRes((obj), (res)); \
+} while (0)
+
+#define NewOCSPBasicRes(klass) \
+    TypedData_Wrap_Struct((klass), &ossl_ocsp_basicresp_type, 0)
+#define SetOCSPBasicRes(obj, res) do { \
+    if(!(res)) ossl_raise(rb_eRuntimeError, "Response wasn't initialized!"); \
+    RTYPEDDATA_DATA(obj) = (res); \
+} while (0)
+#define GetOCSPBasicRes(obj, res) do { \
+    TypedData_Get_Struct((obj), OCSP_BASICRESP, &ossl_ocsp_basicresp_type, (res)); \
+    if(!(res)) ossl_raise(rb_eRuntimeError, "Response wasn't initialized!"); \
+} while (0)
+#define SafeGetOCSPBasicRes(obj, res) do { \
+    OSSL_Check_Kind((obj), cOCSPBasicRes); \
+    GetOCSPBasicRes((obj), (res)); \
+} while (0)
+
+#define NewOCSPSingleRes(klass) \
+    TypedData_Wrap_Struct((klass), &ossl_ocsp_singleresp_type, 0)
+#define SetOCSPSingleRes(obj, res) do { \
+    if(!(res)) ossl_raise(rb_eRuntimeError, "SingleResponse wasn't initialized!"); \
+    RTYPEDDATA_DATA(obj) = (res); \
+} while (0)
+#define GetOCSPSingleRes(obj, res) do { \
+    TypedData_Get_Struct((obj), OCSP_SINGLERESP, &ossl_ocsp_singleresp_type, (res)); \
+    if(!(res)) ossl_raise(rb_eRuntimeError, "SingleResponse wasn't initialized!"); \
+} while (0)
+#define SafeGetOCSPSingleRes(obj, res) do { \
+    OSSL_Check_Kind((obj), cOCSPSingleRes); \
+    GetOCSPSingleRes((obj), (res)); \
+} while (0)
+
+#define NewOCSPCertId(klass) \
+    TypedData_Wrap_Struct((klass), &ossl_ocsp_certid_type, 0)
+#define SetOCSPCertId(obj, cid) do { \
+    if(!(cid)) ossl_raise(rb_eRuntimeError, "Cert ID wasn't initialized!"); \
+    RTYPEDDATA_DATA(obj) = (cid); \
+} while (0)
+#define GetOCSPCertId(obj, cid) do { \
+    TypedData_Get_Struct((obj), OCSP_CERTID, &ossl_ocsp_certid_type, (cid)); \
+    if(!(cid)) ossl_raise(rb_eRuntimeError, "Cert ID wasn't initialized!"); \
+} while (0)
+#define SafeGetOCSPCertId(obj, cid) do { \
+    OSSL_Check_Kind((obj), cOCSPCertId); \
+    GetOCSPCertId((obj), (cid)); \
+} while (0)
+
+VALUE mOCSP;
+VALUE eOCSPError;
+VALUE cOCSPReq;
+VALUE cOCSPRes;
+VALUE cOCSPBasicRes;
+VALUE cOCSPSingleRes;
+VALUE cOCSPCertId;
+
+static void
+ossl_ocsp_request_free(void *ptr)
+{
+    OCSP_REQUEST_free(ptr);
+}
+
+static const rb_data_type_t ossl_ocsp_request_type = {
+    "OpenSSL/OCSP/REQUEST",
+    {
+	0, ossl_ocsp_request_free,
+    },
+    0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
+};
+
+static void
+ossl_ocsp_response_free(void *ptr)
+{
+    OCSP_RESPONSE_free(ptr);
+}
+
+static const rb_data_type_t ossl_ocsp_response_type = {
+    "OpenSSL/OCSP/RESPONSE",
+    {
+	0, ossl_ocsp_response_free,
+    },
+    0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
+};
+
+static void
+ossl_ocsp_basicresp_free(void *ptr)
+{
+    OCSP_BASICRESP_free(ptr);
+}
+
+static const rb_data_type_t ossl_ocsp_basicresp_type = {
+    "OpenSSL/OCSP/BASICRESP",
+    {
+	0, ossl_ocsp_basicresp_free,
+    },
+    0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
+};
+
+static void
+ossl_ocsp_singleresp_free(void *ptr)
+{
+    OCSP_SINGLERESP_free(ptr);
+}
+
+static const rb_data_type_t ossl_ocsp_singleresp_type = {
+    "OpenSSL/OCSP/SINGLERESP",
+    {
+	0, ossl_ocsp_singleresp_free,
+    },
+    0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
+};
+
+static void
+ossl_ocsp_certid_free(void *ptr)
+{
+    OCSP_CERTID_free(ptr);
+}
+
+static const rb_data_type_t ossl_ocsp_certid_type = {
+    "OpenSSL/OCSP/CERTID",
+    {
+	0, ossl_ocsp_certid_free,
+    },
+    0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
+};
+
+/*
+ * Public
+ */
+static VALUE
+ossl_ocspcertid_new(OCSP_CERTID *cid)
+{
+    VALUE obj = NewOCSPCertId(cOCSPCertId);
+    SetOCSPCertId(obj, cid);
+    return obj;
+}
+
+/*
+ * OCSP::Resquest
+ */
+static VALUE
+ossl_ocspreq_alloc(VALUE klass)
+{
+    OCSP_REQUEST *req;
+    VALUE obj;
+
+    obj = NewOCSPReq(klass);
+    if (!(req = OCSP_REQUEST_new()))
+	ossl_raise(eOCSPError, NULL);
+    SetOCSPReq(obj, req);
+
+    return obj;
+}
+
+static VALUE
+ossl_ocspreq_initialize_copy(VALUE self, VALUE other)
+{
+    OCSP_REQUEST *req, *req_old, *req_new;
+
+    rb_check_frozen(self);
+    GetOCSPReq(self, req_old);
+    SafeGetOCSPReq(other, req);
+
+    req_new = ASN1_item_dup(ASN1_ITEM_rptr(OCSP_REQUEST), req);
+    if (!req_new)
+	ossl_raise(eOCSPError, "ASN1_item_dup");
+
+    SetOCSPReq(self, req_new);
+    OCSP_REQUEST_free(req_old);
+
+    return self;
+}
+
+/*
+ * call-seq:
+ *   OpenSSL::OCSP::Request.new              -> request
+ *   OpenSSL::OCSP::Request.new(request_der) -> request
+ *
+ * Creates a new OpenSSL::OCSP::Request.  The request may be created empty or
+ * from a +request_der+ string.
+ */
+
+static VALUE
+ossl_ocspreq_initialize(int argc, VALUE *argv, VALUE self)
+{
+    VALUE arg;
+    OCSP_REQUEST *req, *req_new;
+    const unsigned char *p;
+
+    rb_scan_args(argc, argv, "01", &arg);
+    if(!NIL_P(arg)){
+	GetOCSPReq(self, req);
+	arg = ossl_to_der_if_possible(arg);
+	StringValue(arg);
+	p = (unsigned char *)RSTRING_PTR(arg);
+	req_new = d2i_OCSP_REQUEST(NULL, &p, RSTRING_LEN(arg));
+	if (!req_new)
+	    ossl_raise(eOCSPError, "d2i_OCSP_REQUEST");
+	SetOCSPReq(self, req_new);
+	OCSP_REQUEST_free(req);
+    }
+
+    return self;
+}
+
+/*
+ * call-seq:
+ *   request.add_nonce(nonce = nil) -> request
+ *
+ * Adds a +nonce+ to the OCSP request.  If no nonce is given a random one will
+ * be generated.
+ *
+ * The nonce is used to prevent replay attacks but some servers do not support
+ * it.
+ */
+
+static VALUE
+ossl_ocspreq_add_nonce(int argc, VALUE *argv, VALUE self)
+{
+    OCSP_REQUEST *req;
+    VALUE val;
+    int ret;
+
+    rb_scan_args(argc, argv, "01", &val);
+    if(NIL_P(val)) {
+	GetOCSPReq(self, req);
+	ret = OCSP_request_add1_nonce(req, NULL, -1);
+    }
+    else{
+	StringValue(val);
+	GetOCSPReq(self, req);
+	ret = OCSP_request_add1_nonce(req, (unsigned char *)RSTRING_PTR(val), RSTRING_LENINT(val));
+    }
+    if(!ret) ossl_raise(eOCSPError, NULL);
+
+    return self;
+}
+
+/*
+ * call-seq:
+ *   request.check_nonce(response) -> result
+ *
+ * Checks the nonce validity for this request and +response+.
+ *
+ * The return value is one of the following:
+ *
+ * -1 :: nonce in request only.
+ *  0 :: nonces both present and not equal.
+ *  1 :: nonces present and equal.
+ *  2 :: nonces both absent.
+ *  3 :: nonce present in response only.
+ *
+ * For most responses, clients can check +result+ > 0.  If a responder doesn't
+ * handle nonces <code>result.nonzero?</code> may be necessary.  A result of
+ * <code>0</code> is always an error.
+ */
+
+static VALUE
+ossl_ocspreq_check_nonce(VALUE self, VALUE basic_resp)
+{
+    OCSP_REQUEST *req;
+    OCSP_BASICRESP *bs;
+    int res;
+
+    GetOCSPReq(self, req);
+    SafeGetOCSPBasicRes(basic_resp, bs);
+    res = OCSP_check_nonce(req, bs);
+
+    return INT2NUM(res);
+}
+
+/*
+ * call-seq:
+ *   request.add_certid(certificate_id) -> request
+ *
+ * Adds +certificate_id+ to the request.
+ */
+
+static VALUE
+ossl_ocspreq_add_certid(VALUE self, VALUE certid)
+{
+    OCSP_REQUEST *req;
+    OCSP_CERTID *id, *id_new;
+
+    GetOCSPReq(self, req);
+    GetOCSPCertId(certid, id);
+
+    if (!(id_new = OCSP_CERTID_dup(id)))
+	ossl_raise(eOCSPError, "OCSP_CERTID_dup");
+    if (!OCSP_request_add0_id(req, id_new)) {
+	OCSP_CERTID_free(id_new);
+	ossl_raise(eOCSPError, "OCSP_request_add0_id");
+    }
+
+    return self;
+}
+
+/*
+ * call-seq:
+ *   request.certid -> [certificate_id, ...]
+ *
+ * Returns all certificate IDs in this request.
+ */
+
+static VALUE
+ossl_ocspreq_get_certid(VALUE self)
+{
+    OCSP_REQUEST *req;
+    OCSP_ONEREQ *one;
+    OCSP_CERTID *id;
+    VALUE ary, tmp;
+    int i, count;
+
+    GetOCSPReq(self, req);
+    count = OCSP_request_onereq_count(req);
+    ary = (count > 0) ? rb_ary_new() : Qnil;
+    for(i = 0; i < count; i++){
+	one = OCSP_request_onereq_get0(req, i);
+	tmp = NewOCSPCertId(cOCSPCertId);
+	if(!(id = OCSP_CERTID_dup(OCSP_onereq_get0_id(one))))
+	    ossl_raise(eOCSPError, NULL);
+	SetOCSPCertId(tmp, id);
+	rb_ary_push(ary, tmp);
+    }
+
+    return ary;
+}
+
+/*
+ * call-seq:
+ *   request.sign(cert, key, certs = nil, flags = 0, digest = nil) -> self
+ *
+ * Signs this OCSP request using +cert+, +key+ and optional +digest+. If
+ * +digest+ is not specified, SHA-1 is used. +certs+ is an optional Array of
+ * additional certificates which are included in the request in addition to
+ * the signer certificate. Note that if +certs+ is nil or not given, flag
+ * OpenSSL::OCSP::NOCERTS is enabled. Pass an empty array to include only the
+ * signer certificate.
+ *
+ * +flags+ can be a bitwise OR of the following constants:
+ *
+ * OpenSSL::OCSP::NOCERTS::
+ *   Don't include any certificates in the request. +certs+ will be ignored.
+ */
+static VALUE
+ossl_ocspreq_sign(int argc, VALUE *argv, VALUE self)
+{
+    VALUE signer_cert, signer_key, certs, flags, digest;
+    OCSP_REQUEST *req;
+    X509 *signer;
+    EVP_PKEY *key;
+    STACK_OF(X509) *x509s = NULL;
+    unsigned long flg = 0;
+    const EVP_MD *md;
+    int ret;
+
+    rb_scan_args(argc, argv, "23", &signer_cert, &signer_key, &certs, &flags, &digest);
+    GetOCSPReq(self, req);
+    signer = GetX509CertPtr(signer_cert);
+    key = GetPrivPKeyPtr(signer_key);
+    if (!NIL_P(flags))
+	flg = NUM2INT(flags);
+    if (NIL_P(digest))
+	md = EVP_sha1();
+    else
+	md = GetDigestPtr(digest);
+    if (NIL_P(certs))
+	flg |= OCSP_NOCERTS;
+    else
+	x509s = ossl_x509_ary2sk(certs);
+
+    ret = OCSP_request_sign(req, signer, key, md, x509s, flg);
+    sk_X509_pop_free(x509s, X509_free);
+    if (!ret) ossl_raise(eOCSPError, NULL);
+
+    return self;
+}
+
+/*
+ * call-seq:
+ *   request.verify(certificates, store, flags = 0) -> true or false
+ *
+ * Verifies this request using the given +certificates+ and +store+.
+ * +certificates+ is an array of OpenSSL::X509::Certificate, +store+ is an
+ * OpenSSL::X509::Store.
+ */
+
+static VALUE
+ossl_ocspreq_verify(int argc, VALUE *argv, VALUE self)
+{
+    VALUE certs, store, flags;
+    OCSP_REQUEST *req;
+    STACK_OF(X509) *x509s;
+    X509_STORE *x509st;
+    int flg, result;
+
+    rb_scan_args(argc, argv, "21", &certs, &store, &flags);
+    GetOCSPReq(self, req);
+    x509st = GetX509StorePtr(store);
+    flg = NIL_P(flags) ? 0 : NUM2INT(flags);
+    x509s = ossl_x509_ary2sk(certs);
+    result = OCSP_request_verify(req, x509s, x509st, flg);
+    sk_X509_pop_free(x509s, X509_free);
+    if (result <= 0)
+	ossl_clear_error();
+
+    return result > 0 ? Qtrue : Qfalse;
+}
+
+/*
+ * Returns this request as a DER-encoded string
+ */
+
+static VALUE
+ossl_ocspreq_to_der(VALUE self)
+{
+    OCSP_REQUEST *req;
+    VALUE str;
+    unsigned char *p;
+    long len;
+
+    GetOCSPReq(self, req);
+    if((len = i2d_OCSP_REQUEST(req, NULL)) <= 0)
+	ossl_raise(eOCSPError, NULL);
+    str = rb_str_new(0, len);
+    p = (unsigned char *)RSTRING_PTR(str);
+    if(i2d_OCSP_REQUEST(req, &p) <= 0)
+	ossl_raise(eOCSPError, NULL);
+    ossl_str_adjust(str, p);
+
+    return str;
+}
+
+/*
+ * OCSP::Response
+ */
+
+/* call-seq:
+ *   OpenSSL::OCSP::Response.create(status, basic_response = nil) -> response
+ *
+ * Creates an OpenSSL::OCSP::Response from +status+ and +basic_response+.
+ */
+
+static VALUE
+ossl_ocspres_s_create(VALUE klass, VALUE status, VALUE basic_resp)
+{
+    OCSP_BASICRESP *bs;
+    OCSP_RESPONSE *res;
+    VALUE obj;
+    int st = NUM2INT(status);
+
+    if(NIL_P(basic_resp)) bs = NULL;
+    else GetOCSPBasicRes(basic_resp, bs); /* NO NEED TO DUP */
+    obj = NewOCSPRes(klass);
+    if(!(res = OCSP_response_create(st, bs)))
+	ossl_raise(eOCSPError, NULL);
+    SetOCSPRes(obj, res);
+
+    return obj;
+}
+
+static VALUE
+ossl_ocspres_alloc(VALUE klass)
+{
+    OCSP_RESPONSE *res;
+    VALUE obj;
+
+    obj = NewOCSPRes(klass);
+    if(!(res = OCSP_RESPONSE_new()))
+	ossl_raise(eOCSPError, NULL);
+    SetOCSPRes(obj, res);
+
+    return obj;
+}
+
+static VALUE
+ossl_ocspres_initialize_copy(VALUE self, VALUE other)
+{
+    OCSP_RESPONSE *res, *res_old, *res_new;
+
+    rb_check_frozen(self);
+    GetOCSPRes(self, res_old);
+    SafeGetOCSPRes(other, res);
+
+    res_new = ASN1_item_dup(ASN1_ITEM_rptr(OCSP_RESPONSE), res);
+    if (!res_new)
+	ossl_raise(eOCSPError, "ASN1_item_dup");
+
+    SetOCSPRes(self, res_new);
+    OCSP_RESPONSE_free(res_old);
+
+    return self;
+}
+
+/*
+ * call-seq:
+ *   OpenSSL::OCSP::Response.new               -> response
+ *   OpenSSL::OCSP::Response.new(response_der) -> response
+ *
+ * Creates a new OpenSSL::OCSP::Response.  The response may be created empty or
+ * from a +response_der+ string.
+ */
+
+static VALUE
+ossl_ocspres_initialize(int argc, VALUE *argv, VALUE self)
+{
+    VALUE arg;
+    OCSP_RESPONSE *res, *res_new;
+    const unsigned char *p;
+
+    rb_scan_args(argc, argv, "01", &arg);
+    if(!NIL_P(arg)){
+	GetOCSPRes(self, res);
+	arg = ossl_to_der_if_possible(arg);
+	StringValue(arg);
+	p = (unsigned char *)RSTRING_PTR(arg);
+	res_new = d2i_OCSP_RESPONSE(NULL, &p, RSTRING_LEN(arg));
+	if (!res_new)
+	    ossl_raise(eOCSPError, "d2i_OCSP_RESPONSE");
+	SetOCSPRes(self, res_new);
+	OCSP_RESPONSE_free(res);
+    }
+
+    return self;
+}
+
+/*
+ * call-seq:
+ *   response.status -> Integer
+ *
+ * Returns the status of the response.
+ */
+
+static VALUE
+ossl_ocspres_status(VALUE self)
+{
+    OCSP_RESPONSE *res;
+    int st;
+
+    GetOCSPRes(self, res);
+    st = OCSP_response_status(res);
+
+    return INT2NUM(st);
+}
+
+/*
+ * call-seq:
+ *   response.status_string -> String
+ *
+ * Returns a status string for the response.
+ */
+
+static VALUE
+ossl_ocspres_status_string(VALUE self)
+{
+    OCSP_RESPONSE *res;
+    int st;
+
+    GetOCSPRes(self, res);
+    st = OCSP_response_status(res);
+
+    return rb_str_new2(OCSP_response_status_str(st));
+}
+
+/*
+ * call-seq:
+ *   response.basic
+ *
+ * Returns a BasicResponse for this response
+ */
+
+static VALUE
+ossl_ocspres_get_basic(VALUE self)
+{
+    OCSP_RESPONSE *res;
+    OCSP_BASICRESP *bs;
+    VALUE ret;
+
+    GetOCSPRes(self, res);
+    ret = NewOCSPBasicRes(cOCSPBasicRes);
+    if(!(bs = OCSP_response_get1_basic(res)))
+	return Qnil;
+    SetOCSPBasicRes(ret, bs);
+
+    return ret;
+}
+
+/*
+ * call-seq:
+ *   response.to_der -> String
+ *
+ * Returns this response as a DER-encoded string.
+ */
+
+static VALUE
+ossl_ocspres_to_der(VALUE self)
+{
+    OCSP_RESPONSE *res;
+    VALUE str;
+    long len;
+    unsigned char *p;
+
+    GetOCSPRes(self, res);
+    if((len = i2d_OCSP_RESPONSE(res, NULL)) <= 0)
+	ossl_raise(eOCSPError, NULL);
+    str = rb_str_new(0, len);
+    p = (unsigned char *)RSTRING_PTR(str);
+    if(i2d_OCSP_RESPONSE(res, &p) <= 0)
+	ossl_raise(eOCSPError, NULL);
+    ossl_str_adjust(str, p);
+
+    return str;
+}
+
+/*
+ * OCSP::BasicResponse
+ */
+static VALUE
+ossl_ocspbres_alloc(VALUE klass)
+{
+    OCSP_BASICRESP *bs;
+    VALUE obj;
+
+    obj = NewOCSPBasicRes(klass);
+    if(!(bs = OCSP_BASICRESP_new()))
+	ossl_raise(eOCSPError, NULL);
+    SetOCSPBasicRes(obj, bs);
+
+    return obj;
+}
+
+static VALUE
+ossl_ocspbres_initialize_copy(VALUE self, VALUE other)
+{
+    OCSP_BASICRESP *bs, *bs_old, *bs_new;
+
+    rb_check_frozen(self);
+    GetOCSPBasicRes(self, bs_old);
+    SafeGetOCSPBasicRes(other, bs);
+
+    bs_new = ASN1_item_dup(ASN1_ITEM_rptr(OCSP_BASICRESP), bs);
+    if (!bs_new)
+	ossl_raise(eOCSPError, "ASN1_item_dup");
+
+    SetOCSPBasicRes(self, bs_new);
+    OCSP_BASICRESP_free(bs_old);
+
+    return self;
+}
+
+/*
+ * call-seq:
+ *   OpenSSL::OCSP::BasicResponse.new(der_string = nil) -> basic_response
+ *
+ * Creates a new BasicResponse. If +der_string+ is given, decodes +der_string+
+ * as DER.
+ */
+
+static VALUE
+ossl_ocspbres_initialize(int argc, VALUE *argv, VALUE self)
+{
+    VALUE arg;
+    OCSP_BASICRESP *res, *res_new;
+    const unsigned char *p;
+
+    rb_scan_args(argc, argv, "01", &arg);
+    if (!NIL_P(arg)) {
+	GetOCSPBasicRes(self, res);
+	arg = ossl_to_der_if_possible(arg);
+	StringValue(arg);
+	p = (unsigned char *)RSTRING_PTR(arg);
+	res_new = d2i_OCSP_BASICRESP(NULL, &p, RSTRING_LEN(arg));
+	if (!res_new)
+	    ossl_raise(eOCSPError, "d2i_OCSP_BASICRESP");
+	SetOCSPBasicRes(self, res_new);
+	OCSP_BASICRESP_free(res);
+    }
+
+    return self;
+}
+
+/*
+ * call-seq:
+ *   basic_response.copy_nonce(request) -> Integer
+ *
+ * Copies the nonce from +request+ into this response.  Returns 1 on success
+ * and 0 on failure.
+ */
+
+static VALUE
+ossl_ocspbres_copy_nonce(VALUE self, VALUE request)
+{
+    OCSP_BASICRESP *bs;
+    OCSP_REQUEST *req;
+    int ret;
+
+    GetOCSPBasicRes(self, bs);
+    SafeGetOCSPReq(request, req);
+    ret = OCSP_copy_nonce(bs, req);
+
+    return INT2NUM(ret);
+}
+
+/*
+ * call-seq:
+ *   basic_response.add_nonce(nonce = nil)
+ *
+ * Adds +nonce+ to this response.  If no nonce was provided a random nonce
+ * will be added.
+ */
+
+static VALUE
+ossl_ocspbres_add_nonce(int argc, VALUE *argv, VALUE self)
+{
+    OCSP_BASICRESP *bs;
+    VALUE val;
+    int ret;
+
+    rb_scan_args(argc, argv, "01", &val);
+    if(NIL_P(val)) {
+	GetOCSPBasicRes(self, bs);
+	ret = OCSP_basic_add1_nonce(bs, NULL, -1);
+    }
+    else{
+	StringValue(val);
+	GetOCSPBasicRes(self, bs);
+	ret = OCSP_basic_add1_nonce(bs, (unsigned char *)RSTRING_PTR(val), RSTRING_LENINT(val));
+    }
+    if(!ret) ossl_raise(eOCSPError, NULL);
+
+    return self;
+}
+
+static VALUE
+add_status_convert_time(VALUE obj)
+{
+    ASN1_TIME *time;
+
+    if (RB_INTEGER_TYPE_P(obj))
+	time = X509_gmtime_adj(NULL, NUM2INT(obj));
+    else
+	time = ossl_x509_time_adjust(NULL, obj);
+
+    if (!time)
+	ossl_raise(eOCSPError, NULL);
+
+    return (VALUE)time;
+}
+
+/*
+ * call-seq:
+ *   basic_response.add_status(certificate_id, status, reason, revocation_time, this_update, next_update, extensions) -> basic_response
+ *
+ * Adds a certificate status for +certificate_id+. +status+ is the status, and
+ * must be one of these:
+ *
+ * - OpenSSL::OCSP::V_CERTSTATUS_GOOD
+ * - OpenSSL::OCSP::V_CERTSTATUS_REVOKED
+ * - OpenSSL::OCSP::V_CERTSTATUS_UNKNOWN
+ *
+ * +reason+ and +revocation_time+ can be given only when +status+ is
+ * OpenSSL::OCSP::V_CERTSTATUS_REVOKED. +reason+ describes the reason for the
+ * revocation, and must be one of OpenSSL::OCSP::REVOKED_STATUS_* constants.
+ * +revocation_time+ is the time when the certificate is revoked.
+ *
+ * +this_update+ and +next_update+ indicate the time at which ths status is
+ * verified to be correct and the time at or before which newer information
+ * will be available, respectively. +next_update+ is optional.
+ *
+ * +extensions+ is an Array of OpenSSL::X509::Extension to be included in the
+ * SingleResponse. This is also optional.
+ *
+ * Note that the times, +revocation_time+, +this_update+ and +next_update+
+ * can be specified in either of Integer or Time object. If they are Integer, it
+ * is treated as the relative seconds from the current time.
+ */
+static VALUE
+ossl_ocspbres_add_status(VALUE self, VALUE cid, VALUE status,
+			 VALUE reason, VALUE revtime,
+			 VALUE thisupd, VALUE nextupd, VALUE ext)
+{
+    OCSP_BASICRESP *bs;
+    OCSP_SINGLERESP *single;
+    OCSP_CERTID *id;
+    ASN1_TIME *ths = NULL, *nxt = NULL, *rev = NULL;
+    int st, rsn = 0, error = 0, rstatus = 0;
+    long i;
+    VALUE tmp;
+
+    GetOCSPBasicRes(self, bs);
+    SafeGetOCSPCertId(cid, id);
+    st = NUM2INT(status);
+    if (!NIL_P(ext)) { /* All ext's members must be X509::Extension */
+	ext = rb_check_array_type(ext);
+	for (i = 0; i < RARRAY_LEN(ext); i++)
+	    OSSL_Check_Kind(RARRAY_AREF(ext, i), cX509Ext);
+    }
+
+    if (st == V_OCSP_CERTSTATUS_REVOKED) {
+	rsn = NUM2INT(reason);
+	tmp = rb_protect(add_status_convert_time, revtime, &rstatus);
+	if (rstatus) goto err;
+	rev = (ASN1_TIME *)tmp;
+    }
+
+    tmp = rb_protect(add_status_convert_time, thisupd, &rstatus);
+    if (rstatus) goto err;
+    ths = (ASN1_TIME *)tmp;
+
+    if (!NIL_P(nextupd)) {
+	tmp = rb_protect(add_status_convert_time, nextupd, &rstatus);
+	if (rstatus) goto err;
+	nxt = (ASN1_TIME *)tmp;
+    }
+
+    if(!(single = OCSP_basic_add1_status(bs, id, st, rsn, rev, ths, nxt))){
+	error = 1;
+	goto err;
+    }
+
+    if(!NIL_P(ext)){
+	X509_EXTENSION *x509ext;
+
+	for(i = 0; i < RARRAY_LEN(ext); i++){
+	    x509ext = GetX509ExtPtr(RARRAY_AREF(ext, i));
+	    if(!OCSP_SINGLERESP_add_ext(single, x509ext, -1)){
+		error = 1;
+		goto err;
+	    }
+	}
+    }
+
+ err:
+    ASN1_TIME_free(ths);
+    ASN1_TIME_free(nxt);
+    ASN1_TIME_free(rev);
+    if(error) ossl_raise(eOCSPError, NULL);
+    if(rstatus) rb_jump_tag(rstatus);
+
+    return self;
+}
+
+/*
+ * call-seq:
+ *   basic_response.status -> statuses
+ *
+ * Returns an Array of statuses for this response.  Each status contains a
+ * CertificateId, the status (0 for good, 1 for revoked, 2 for unknown), the
+ * reason for the status, the revocation time, the time of this update, the time
+ * for the next update and a list of OpenSSL::X509::Extensions.
+ *
+ * This should be superseded by BasicResponse#responses and #find_response that
+ * return SingleResponse.
+ */
+static VALUE
+ossl_ocspbres_get_status(VALUE self)
+{
+    OCSP_BASICRESP *bs;
+    OCSP_SINGLERESP *single;
+    OCSP_CERTID *cid;
+    ASN1_TIME *revtime, *thisupd, *nextupd;
+    int status, reason;
+    X509_EXTENSION *x509ext;
+    VALUE ret, ary, ext;
+    int count, ext_count, i, j;
+
+    GetOCSPBasicRes(self, bs);
+    ret = rb_ary_new();
+    count = OCSP_resp_count(bs);
+    for(i = 0; i < count; i++){
+	single = OCSP_resp_get0(bs, i);
+	if(!single) continue;
+
+	revtime = thisupd = nextupd = NULL;
+	status = OCSP_single_get0_status(single, &reason, &revtime,
+					 &thisupd, &nextupd);
+	if(status < 0) continue;
+	if(!(cid = OCSP_CERTID_dup((OCSP_CERTID *)OCSP_SINGLERESP_get0_id(single)))) /* FIXME */
+	    ossl_raise(eOCSPError, NULL);
+	ary = rb_ary_new();
+	rb_ary_push(ary, ossl_ocspcertid_new(cid));
+	rb_ary_push(ary, INT2NUM(status));
+	rb_ary_push(ary, INT2NUM(reason));
+	rb_ary_push(ary, revtime ? asn1time_to_time(revtime) : Qnil);
+	rb_ary_push(ary, thisupd ? asn1time_to_time(thisupd) : Qnil);
+	rb_ary_push(ary, nextupd ? asn1time_to_time(nextupd) : Qnil);
+	ext = rb_ary_new();
+	ext_count = OCSP_SINGLERESP_get_ext_count(single);
+	for(j = 0; j < ext_count; j++){
+	    x509ext = OCSP_SINGLERESP_get_ext(single, j);
+	    rb_ary_push(ext, ossl_x509ext_new(x509ext));
+	}
+	rb_ary_push(ary, ext);
+	rb_ary_push(ret, ary);
+    }
+
+    return ret;
+}
+
+static VALUE ossl_ocspsres_new(OCSP_SINGLERESP *);
+
+/*
+ * call-seq:
+ *   basic_response.responses -> Array of SingleResponse
+ *
+ * Returns an Array of SingleResponse for this BasicResponse.
+ */
+
+static VALUE
+ossl_ocspbres_get_responses(VALUE self)
+{
+    OCSP_BASICRESP *bs;
+    VALUE ret;
+    int count, i;
+
+    GetOCSPBasicRes(self, bs);
+    count = OCSP_resp_count(bs);
+    ret = rb_ary_new2(count);
+
+    for (i = 0; i < count; i++) {
+	OCSP_SINGLERESP *sres, *sres_new;
+
+	sres = OCSP_resp_get0(bs, i);
+	sres_new = ASN1_item_dup(ASN1_ITEM_rptr(OCSP_SINGLERESP), sres);
+	if (!sres_new)
+	    ossl_raise(eOCSPError, "ASN1_item_dup");
+
+	rb_ary_push(ret, ossl_ocspsres_new(sres_new));
+    }
+
+    return ret;
+}
+
+
+/*
+ * call-seq:
+ *   basic_response.find_response(certificate_id) -> SingleResponse | nil
+ *
+ * Returns a SingleResponse whose CertId matches with +certificate_id+, or nil
+ * if this BasicResponse does not contain it.
+ */
+static VALUE
+ossl_ocspbres_find_response(VALUE self, VALUE target)
+{
+    OCSP_BASICRESP *bs;
+    OCSP_SINGLERESP *sres, *sres_new;
+    OCSP_CERTID *id;
+    int n;
+
+    SafeGetOCSPCertId(target, id);
+    GetOCSPBasicRes(self, bs);
+
+    if ((n = OCSP_resp_find(bs, id, -1)) == -1)
+	return Qnil;
+
+    sres = OCSP_resp_get0(bs, n);
+    sres_new = ASN1_item_dup(ASN1_ITEM_rptr(OCSP_SINGLERESP), sres);
+    if (!sres_new)
+	ossl_raise(eOCSPError, "ASN1_item_dup");
+
+    return ossl_ocspsres_new(sres_new);
+}
+
+/*
+ * call-seq:
+ *   basic_response.sign(cert, key, certs = nil, flags = 0, digest = nil) -> self
+ *
+ * Signs this OCSP response using the +cert+, +key+ and optional +digest+. This
+ * behaves in the similar way as OpenSSL::OCSP::Request#sign.
+ *
+ * +flags+ can include:
+ * OpenSSL::OCSP::NOCERTS::    don't include certificates
+ * OpenSSL::OCSP::NOTIME::     don't set producedAt
+ * OpenSSL::OCSP::RESPID_KEY:: use signer's public key hash as responderID
+ */
+
+static VALUE
+ossl_ocspbres_sign(int argc, VALUE *argv, VALUE self)
+{
+    VALUE signer_cert, signer_key, certs, flags, digest;
+    OCSP_BASICRESP *bs;
+    X509 *signer;
+    EVP_PKEY *key;
+    STACK_OF(X509) *x509s = NULL;
+    unsigned long flg = 0;
+    const EVP_MD *md;
+    int ret;
+
+    rb_scan_args(argc, argv, "23", &signer_cert, &signer_key, &certs, &flags, &digest);
+    GetOCSPBasicRes(self, bs);
+    signer = GetX509CertPtr(signer_cert);
+    key = GetPrivPKeyPtr(signer_key);
+    if (!NIL_P(flags))
+	flg = NUM2INT(flags);
+    if (NIL_P(digest))
+	md = EVP_sha1();
+    else
+	md = GetDigestPtr(digest);
+    if (NIL_P(certs))
+	flg |= OCSP_NOCERTS;
+    else
+	x509s = ossl_x509_ary2sk(certs);
+
+    ret = OCSP_basic_sign(bs, signer, key, md, x509s, flg);
+    sk_X509_pop_free(x509s, X509_free);
+    if (!ret) ossl_raise(eOCSPError, NULL);
+
+    return self;
+}
+
+/*
+ * call-seq:
+ *   basic_response.verify(certificates, store, flags = 0) -> true or false
+ *
+ * Verifies the signature of the response using the given +certificates+ and
+ * +store+. This works in the similar way as OpenSSL::OCSP::Request#verify.
+ */
+static VALUE
+ossl_ocspbres_verify(int argc, VALUE *argv, VALUE self)
+{
+    VALUE certs, store, flags;
+    OCSP_BASICRESP *bs;
+    STACK_OF(X509) *x509s;
+    X509_STORE *x509st;
+    int flg, result;
+
+    rb_scan_args(argc, argv, "21", &certs, &store, &flags);
+    GetOCSPBasicRes(self, bs);
+    x509st = GetX509StorePtr(store);
+    flg = NIL_P(flags) ? 0 : NUM2INT(flags);
+    x509s = ossl_x509_ary2sk(certs);
+#if (OPENSSL_VERSION_NUMBER < 0x1000202fL) || defined(LIBRESSL_VERSION_NUMBER)
+    /*
+     * OpenSSL had a bug that it doesn't use the certificates in x509s for
+     * verifying the chain. This can be a problem when the response is signed by
+     * a certificate issued by an intermediate CA.
+     *
+     *       root_ca
+     *         |
+     *   intermediate_ca
+     *         |-------------|
+     *     end_entity    ocsp_signer
+     *
+     * When the certificate hierarchy is like this, and the response contains
+     * only ocsp_signer certificate, the following code wrongly fails.
+     *
+     *   store = OpenSSL::X509::Store.new; store.add_cert(root_ca)
+     *   basic_response.verify([intermediate_ca], store)
+     *
+     * So add the certificates in x509s to the embedded certificates list first.
+     *
+     * This is fixed in OpenSSL 0.9.8zg, 1.0.0s, 1.0.1n, 1.0.2b. But it still
+     * exists in LibreSSL 2.1.10, 2.2.9, 2.3.6, 2.4.1.
+     */
+    if (!(flg & (OCSP_NOCHAIN | OCSP_NOVERIFY)) &&
+	sk_X509_num(x509s) && sk_X509_num(bs->certs)) {
+	int i;
+
+	bs = ASN1_item_dup(ASN1_ITEM_rptr(OCSP_BASICRESP), bs);
+	if (!bs) {
+	    sk_X509_pop_free(x509s, X509_free);
+	    ossl_raise(eOCSPError, "ASN1_item_dup");
+	}
+
+	for (i = 0; i < sk_X509_num(x509s); i++) {
+	    if (!OCSP_basic_add1_cert(bs, sk_X509_value(x509s, i))) {
+		sk_X509_pop_free(x509s, X509_free);
+		OCSP_BASICRESP_free(bs);
+		ossl_raise(eOCSPError, "OCSP_basic_add1_cert");
+	    }
+	}
+	result = OCSP_basic_verify(bs, x509s, x509st, flg);
+	OCSP_BASICRESP_free(bs);
+    }
+    else {
+	result = OCSP_basic_verify(bs, x509s, x509st, flg);
+    }
+#else
+    result = OCSP_basic_verify(bs, x509s, x509st, flg);
+#endif
+    sk_X509_pop_free(x509s, X509_free);
+    if (result <= 0)
+	ossl_clear_error();
+
+    return result > 0 ? Qtrue : Qfalse;
+}
+
+/*
+ * call-seq:
+ *   basic_response.to_der -> String
+ *
+ * Encodes this basic response into a DER-encoded string.
+ */
+static VALUE
+ossl_ocspbres_to_der(VALUE self)
+{
+    OCSP_BASICRESP *res;
+    VALUE str;
+    long len;
+    unsigned char *p;
+
+    GetOCSPBasicRes(self, res);
+    if ((len = i2d_OCSP_BASICRESP(res, NULL)) <= 0)
+	ossl_raise(eOCSPError, NULL);
+    str = rb_str_new(0, len);
+    p = (unsigned char *)RSTRING_PTR(str);
+    if (i2d_OCSP_BASICRESP(res, &p) <= 0)
+	ossl_raise(eOCSPError, NULL);
+    ossl_str_adjust(str, p);
+
+    return str;
+}
+
+/*
+ * OCSP::SingleResponse
+ */
+static VALUE
+ossl_ocspsres_new(OCSP_SINGLERESP *sres)
+{
+    VALUE obj;
+
+    obj = NewOCSPSingleRes(cOCSPSingleRes);
+    SetOCSPSingleRes(obj, sres);
+
+    return obj;
+}
+
+static VALUE
+ossl_ocspsres_alloc(VALUE klass)
+{
+    OCSP_SINGLERESP *sres;
+    VALUE obj;
+
+    obj = NewOCSPSingleRes(klass);
+    if (!(sres = OCSP_SINGLERESP_new()))
+	ossl_raise(eOCSPError, NULL);
+    SetOCSPSingleRes(obj, sres);
+
+    return obj;
+}
+
+/*
+ * call-seq:
+ *   OpenSSL::OCSP::SingleResponse.new(der_string) -> SingleResponse
+ *
+ * Creates a new SingleResponse from +der_string+.
+ */
+static VALUE
+ossl_ocspsres_initialize(VALUE self, VALUE arg)
+{
+    OCSP_SINGLERESP *res, *res_new;
+    const unsigned char *p;
+
+    arg = ossl_to_der_if_possible(arg);
+    StringValue(arg);
+    GetOCSPSingleRes(self, res);
+
+    p = (unsigned char*)RSTRING_PTR(arg);
+    res_new = d2i_OCSP_SINGLERESP(NULL, &p, RSTRING_LEN(arg));
+    if (!res_new)
+	ossl_raise(eOCSPError, "d2i_OCSP_SINGLERESP");
+    SetOCSPSingleRes(self, res_new);
+    OCSP_SINGLERESP_free(res);
+
+    return self;
+}
+
+static VALUE
+ossl_ocspsres_initialize_copy(VALUE self, VALUE other)
+{
+    OCSP_SINGLERESP *sres, *sres_old, *sres_new;
+
+    rb_check_frozen(self);
+    GetOCSPSingleRes(self, sres_old);
+    SafeGetOCSPSingleRes(other, sres);
+
+    sres_new = ASN1_item_dup(ASN1_ITEM_rptr(OCSP_SINGLERESP), sres);
+    if (!sres_new)
+	ossl_raise(eOCSPError, "ASN1_item_dup");
+
+    SetOCSPSingleRes(self, sres_new);
+    OCSP_SINGLERESP_free(sres_old);
+
+    return self;
+}
+
+/*
+ * call-seq:
+ *   single_response.check_validity(nsec = 0, maxsec = -1) -> true | false
+ *
+ * Checks the validity of thisUpdate and nextUpdate fields of this
+ * SingleResponse. This checks the current time is within the range thisUpdate
+ * to nextUpdate.
+ *
+ * It is possible that the OCSP request takes a few seconds or the time is not
+ * accurate. To avoid rejecting a valid response, this method allows the times
+ * to be within +nsec+ of the current time.
+ *
+ * Some responders don't set the nextUpdate field. This may cause a very old
+ * response to be considered valid. The +maxsec+ parameter can be used to limit
+ * the age of responses.
+ */
+static VALUE
+ossl_ocspsres_check_validity(int argc, VALUE *argv, VALUE self)
+{
+    OCSP_SINGLERESP *sres;
+    ASN1_GENERALIZEDTIME *this_update, *next_update;
+    VALUE nsec_v, maxsec_v;
+    int nsec, maxsec, status, ret;
+
+    rb_scan_args(argc, argv, "02", &nsec_v, &maxsec_v);
+    nsec = NIL_P(nsec_v) ? 0 : NUM2INT(nsec_v);
+    maxsec = NIL_P(maxsec_v) ? -1 : NUM2INT(maxsec_v);
+
+    GetOCSPSingleRes(self, sres);
+    status = OCSP_single_get0_status(sres, NULL, NULL, &this_update, &next_update);
+    if (status < 0)
+	ossl_raise(eOCSPError, "OCSP_single_get0_status");
+
+    ret = OCSP_check_validity(this_update, next_update, nsec, maxsec);
+
+    if (ret)
+	return Qtrue;
+    else {
+	ossl_clear_error();
+	return Qfalse;
+    }
+}
+
+/*
+ * call-seq:
+ *   single_response.certid -> CertificateId
+ *
+ * Returns the CertificateId for which this SingleResponse is.
+ */
+static VALUE
+ossl_ocspsres_get_certid(VALUE self)
+{
+    OCSP_SINGLERESP *sres;
+    OCSP_CERTID *id;
+
+    GetOCSPSingleRes(self, sres);
+    id = OCSP_CERTID_dup((OCSP_CERTID *)OCSP_SINGLERESP_get0_id(sres)); /* FIXME */
+
+    return ossl_ocspcertid_new(id);
+}
+
+/*
+ * call-seq:
+ *   single_response.cert_status -> Integer
+ *
+ * Returns the status of the certificate identified by the certid.
+ * The return value may be one of these constant:
+ *
+ * - V_CERTSTATUS_GOOD
+ * - V_CERTSTATUS_REVOKED
+ * - V_CERTSTATUS_UNKNOWN
+ *
+ * When the status is V_CERTSTATUS_REVOKED, the time at which the certificate
+ * was revoked can be retrieved by #revocation_time.
+ */
+static VALUE
+ossl_ocspsres_get_cert_status(VALUE self)
+{
+    OCSP_SINGLERESP *sres;
+    int status;
+
+    GetOCSPSingleRes(self, sres);
+    status = OCSP_single_get0_status(sres, NULL, NULL, NULL, NULL);
+    if (status < 0)
+	ossl_raise(eOCSPError, "OCSP_single_get0_status");
+
+    return INT2NUM(status);
+}
+
+/*
+ * call-seq:
+ *   single_response.this_update -> Time
+ */
+static VALUE
+ossl_ocspsres_get_this_update(VALUE self)
+{
+    OCSP_SINGLERESP *sres;
+    int status;
+    ASN1_GENERALIZEDTIME *time;
+
+    GetOCSPSingleRes(self, sres);
+    status = OCSP_single_get0_status(sres, NULL, NULL, &time, NULL);
+    if (status < 0)
+	ossl_raise(eOCSPError, "OCSP_single_get0_status");
+
+    return asn1time_to_time(time); /* will handle NULL */
+}
+
+/*
+ * call-seq:
+ *   single_response.next_update -> Time | nil
+ */
+static VALUE
+ossl_ocspsres_get_next_update(VALUE self)
+{
+    OCSP_SINGLERESP *sres;
+    int status;
+    ASN1_GENERALIZEDTIME *time;
+
+    GetOCSPSingleRes(self, sres);
+    status = OCSP_single_get0_status(sres, NULL, NULL, NULL, &time);
+    if (status < 0)
+	ossl_raise(eOCSPError, "OCSP_single_get0_status");
+
+    return asn1time_to_time(time);
+}
+
+/*
+ * call-seq:
+ *   single_response.revocation_time -> Time | nil
+ */
+static VALUE
+ossl_ocspsres_get_revocation_time(VALUE self)
+{
+    OCSP_SINGLERESP *sres;
+    int status;
+    ASN1_GENERALIZEDTIME *time;
+
+    GetOCSPSingleRes(self, sres);
+    status = OCSP_single_get0_status(sres, NULL, &time, NULL, NULL);
+    if (status < 0)
+	ossl_raise(eOCSPError, "OCSP_single_get0_status");
+    if (status != V_OCSP_CERTSTATUS_REVOKED)
+	ossl_raise(eOCSPError, "certificate is not revoked");
+
+    return asn1time_to_time(time);
+}
+
+/*
+ * call-seq:
+ *   single_response.revocation_reason -> Integer | nil
+ */
+static VALUE
+ossl_ocspsres_get_revocation_reason(VALUE self)
+{
+    OCSP_SINGLERESP *sres;
+    int status, reason;
+
+    GetOCSPSingleRes(self, sres);
+    status = OCSP_single_get0_status(sres, &reason, NULL, NULL, NULL);
+    if (status < 0)
+	ossl_raise(eOCSPError, "OCSP_single_get0_status");
+    if (status != V_OCSP_CERTSTATUS_REVOKED)
+	ossl_raise(eOCSPError, "certificate is not revoked");
+
+    return INT2NUM(reason);
+}
+
+/*
+ * call-seq:
+ *   single_response.extensions -> Array of X509::Extension
+ */
+static VALUE
+ossl_ocspsres_get_extensions(VALUE self)
+{
+    OCSP_SINGLERESP *sres;
+    X509_EXTENSION *ext;
+    int count, i;
+    VALUE ary;
+
+    GetOCSPSingleRes(self, sres);
+
+    count = OCSP_SINGLERESP_get_ext_count(sres);
+    ary = rb_ary_new2(count);
+    for (i = 0; i < count; i++) {
+	ext = OCSP_SINGLERESP_get_ext(sres, i);
+	rb_ary_push(ary, ossl_x509ext_new(ext)); /* will dup */
+    }
+
+    return ary;
+}
+
+/*
+ * call-seq:
+ *   single_response.to_der -> String
+ *
+ * Encodes this SingleResponse into a DER-encoded string.
+ */
+static VALUE
+ossl_ocspsres_to_der(VALUE self)
+{
+    OCSP_SINGLERESP *sres;
+    VALUE str;
+    long len;
+    unsigned char *p;
+
+    GetOCSPSingleRes(self, sres);
+    if ((len = i2d_OCSP_SINGLERESP(sres, NULL)) <= 0)
+	ossl_raise(eOCSPError, NULL);
+    str = rb_str_new(0, len);
+    p = (unsigned char *)RSTRING_PTR(str);
+    if (i2d_OCSP_SINGLERESP(sres, &p) <= 0)
+	ossl_raise(eOCSPError, NULL);
+    ossl_str_adjust(str, p);
+
+    return str;
+}
+
+
+/*
+ * OCSP::CertificateId
+ */
+static VALUE
+ossl_ocspcid_alloc(VALUE klass)
+{
+    OCSP_CERTID *id;
+    VALUE obj;
+
+    obj = NewOCSPCertId(klass);
+    if(!(id = OCSP_CERTID_new()))
+	ossl_raise(eOCSPError, NULL);
+    SetOCSPCertId(obj, id);
+
+    return obj;
+}
+
+static VALUE
+ossl_ocspcid_initialize_copy(VALUE self, VALUE other)
+{
+    OCSP_CERTID *cid, *cid_old, *cid_new;
+
+    rb_check_frozen(self);
+    GetOCSPCertId(self, cid_old);
+    SafeGetOCSPCertId(other, cid);
+
+    cid_new = OCSP_CERTID_dup(cid);
+    if (!cid_new)
+	ossl_raise(eOCSPError, "OCSP_CERTID_dup");
+
+    SetOCSPCertId(self, cid_new);
+    OCSP_CERTID_free(cid_old);
+
+    return self;
+}
+
+/*
+ * call-seq:
+ *   OpenSSL::OCSP::CertificateId.new(subject, issuer, digest = nil) -> certificate_id
+ *   OpenSSL::OCSP::CertificateId.new(der_string)                    -> certificate_id
+ *
+ * Creates a new OpenSSL::OCSP::CertificateId for the given +subject+ and
+ * +issuer+ X509 certificates.  The +digest+ is used to compute the
+ * certificate ID and must be an OpenSSL::Digest instance.
+ *
+ * If only one argument is given, decodes it as DER representation of a
+ * certificate ID.
+ */
+
+static VALUE
+ossl_ocspcid_initialize(int argc, VALUE *argv, VALUE self)
+{
+    OCSP_CERTID *id, *newid;
+    VALUE subject, issuer, digest;
+
+    GetOCSPCertId(self, id);
+    if (rb_scan_args(argc, argv, "12", &subject, &issuer, &digest) == 1) {
+	VALUE arg;
+	const unsigned char *p;
+
+	arg = ossl_to_der_if_possible(subject);
+	StringValue(arg);
+	p = (unsigned char *)RSTRING_PTR(arg);
+	newid = d2i_OCSP_CERTID(NULL, &p, RSTRING_LEN(arg));
+	if (!newid)
+	    ossl_raise(eOCSPError, "d2i_OCSP_CERTID");
+    }
+    else {
+	X509 *x509s, *x509i;
+	const EVP_MD *md;
+
+	x509s = GetX509CertPtr(subject); /* NO NEED TO DUP */
+	x509i = GetX509CertPtr(issuer); /* NO NEED TO DUP */
+	md = !NIL_P(digest) ? GetDigestPtr(digest) : NULL;
+
+	newid = OCSP_cert_to_id(md, x509s, x509i);
+	if (!newid)
+	    ossl_raise(eOCSPError, "OCSP_cert_to_id");
+    }
+
+    SetOCSPCertId(self, newid);
+    OCSP_CERTID_free(id);
+
+    return self;
+}
+
+/*
+ * call-seq:
+ *   certificate_id.cmp(other) -> true or false
+ *
+ * Compares this certificate id with +other+ and returns true if they are the
+ * same.
+ */
+static VALUE
+ossl_ocspcid_cmp(VALUE self, VALUE other)
+{
+    OCSP_CERTID *id, *id2;
+    int result;
+
+    GetOCSPCertId(self, id);
+    SafeGetOCSPCertId(other, id2);
+    result = OCSP_id_cmp(id, id2);
+
+    return (result == 0) ? Qtrue : Qfalse;
+}
+
+/*
+ * call-seq:
+ *   certificate_id.cmp_issuer(other) -> true or false
+ *
+ * Compares this certificate id's issuer with +other+ and returns true if
+ * they are the same.
+ */
+
+static VALUE
+ossl_ocspcid_cmp_issuer(VALUE self, VALUE other)
+{
+    OCSP_CERTID *id, *id2;
+    int result;
+
+    GetOCSPCertId(self, id);
+    SafeGetOCSPCertId(other, id2);
+    result = OCSP_id_issuer_cmp(id, id2);
+
+    return (result == 0) ? Qtrue : Qfalse;
+}
+
+/*
+ * call-seq:
+ *   certificate_id.serial -> Integer
+ *
+ * Returns the serial number of the certificate for which status is being
+ * requested.
+ */
+static VALUE
+ossl_ocspcid_get_serial(VALUE self)
+{
+    OCSP_CERTID *id;
+    ASN1_INTEGER *serial;
+
+    GetOCSPCertId(self, id);
+    OCSP_id_get0_info(NULL, NULL, NULL, &serial, id);
+
+    return asn1integer_to_num(serial);
+}
+
+/*
+ * call-seq:
+ *   certificate_id.issuer_name_hash -> String
+ *
+ * Returns the issuerNameHash of this certificate ID, the hash of the
+ * issuer's distinguished name calculated with the hashAlgorithm.
+ */
+static VALUE
+ossl_ocspcid_get_issuer_name_hash(VALUE self)
+{
+    OCSP_CERTID *id;
+    ASN1_OCTET_STRING *name_hash;
+    VALUE ret;
+
+    GetOCSPCertId(self, id);
+    OCSP_id_get0_info(&name_hash, NULL, NULL, NULL, id);
+
+    ret = rb_str_new(NULL, name_hash->length * 2);
+    ossl_bin2hex(name_hash->data, RSTRING_PTR(ret), name_hash->length);
+
+    return ret;
+}
+
+/*
+ * call-seq:
+ *   certificate_id.issuer_key_hash -> String
+ *
+ * Returns the issuerKeyHash of this certificate ID, the hash of the issuer's
+ * public key.
+ */
+static VALUE
+ossl_ocspcid_get_issuer_key_hash(VALUE self)
+{
+    OCSP_CERTID *id;
+    ASN1_OCTET_STRING *key_hash;
+    VALUE ret;
+
+    GetOCSPCertId(self, id);
+    OCSP_id_get0_info(NULL, NULL, &key_hash, NULL, id);
+
+    ret = rb_str_new(NULL, key_hash->length * 2);
+    ossl_bin2hex(key_hash->data, RSTRING_PTR(ret), key_hash->length);
+
+    return ret;
+}
+
+/*
+ * call-seq:
+ *   certificate_id.hash_algorithm -> String
+ *
+ * Returns the ln (long name) of the hash algorithm used to generate
+ * the issuerNameHash and the issuerKeyHash values.
+ */
+static VALUE
+ossl_ocspcid_get_hash_algorithm(VALUE self)
+{
+    OCSP_CERTID *id;
+    ASN1_OBJECT *oid;
+    BIO *out;
+
+    GetOCSPCertId(self, id);
+    OCSP_id_get0_info(NULL, &oid, NULL, NULL, id);
+
+    if (!(out = BIO_new(BIO_s_mem())))
+	ossl_raise(eOCSPError, "BIO_new");
+
+    if (!i2a_ASN1_OBJECT(out, oid)) {
+	BIO_free(out);
+	ossl_raise(eOCSPError, "i2a_ASN1_OBJECT");
+    }
+    return ossl_membio2str(out);
+}
+
+/*
+ * call-seq:
+ *   certificate_id.to_der -> String
+ *
+ * Encodes this certificate identifier into a DER-encoded string.
+ */
+static VALUE
+ossl_ocspcid_to_der(VALUE self)
+{
+    OCSP_CERTID *id;
+    VALUE str;
+    long len;
+    unsigned char *p;
+
+    GetOCSPCertId(self, id);
+    if ((len = i2d_OCSP_CERTID(id, NULL)) <= 0)
+	ossl_raise(eOCSPError, NULL);
+    str = rb_str_new(0, len);
+    p = (unsigned char *)RSTRING_PTR(str);
+    if (i2d_OCSP_CERTID(id, &p) <= 0)
+	ossl_raise(eOCSPError, NULL);
+    ossl_str_adjust(str, p);
+
+    return str;
+}
+
+void
+Init_ossl_ocsp(void)
+{
+#if 0
+    mOSSL = rb_define_module("OpenSSL");
+    eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError);
+#endif
+
+    /*
+     * OpenSSL::OCSP implements Online Certificate Status Protocol requests
+     * and responses.
+     *
+     * Creating and sending an OCSP request requires a subject certificate
+     * that contains an OCSP URL in an authorityInfoAccess extension and the
+     * issuer certificate for the subject certificate.  First, load the issuer
+     * and subject certificates:
+     *
+     *   subject = OpenSSL::X509::Certificate.new subject_pem
+     *   issuer  = OpenSSL::X509::Certificate.new issuer_pem
+     *
+     * To create the request we need to create a certificate ID for the
+     * subject certificate so the CA knows which certificate we are asking
+     * about:
+     *
+     *   digest = OpenSSL::Digest::SHA1.new
+     *   certificate_id =
+     *     OpenSSL::OCSP::CertificateId.new subject, issuer, digest
+     *
+     * Then create a request and add the certificate ID to it:
+     *
+     *   request = OpenSSL::OCSP::Request.new
+     *   request.add_certid certificate_id
+     *
+     * Adding a nonce to the request protects against replay attacks but not
+     * all CA process the nonce.
+     *
+     *   request.add_nonce
+     *
+     * To submit the request to the CA for verification we need to extract the
+     * OCSP URI from the subject certificate:
+     *
+     *   authority_info_access = subject.extensions.find do |extension|
+     *     extension.oid == 'authorityInfoAccess'
+     *   end
+     *
+     *   descriptions = authority_info_access.value.split "\n"
+     *   ocsp = descriptions.find do |description|
+     *     description.start_with? 'OCSP'
+     *   end
+     *
+     *   require 'uri'
+     *
+     *   ocsp_uri = URI ocsp[/URI:(.*)/, 1]
+     *
+     * To submit the request we'll POST the request to the OCSP URI (per RFC
+     * 2560).  Note that we only handle HTTP requests and don't handle any
+     * redirects in this example, so this is insufficient for serious use.
+     *
+     *   require 'net/http'
+     *
+     *   http_response =
+     *     Net::HTTP.start ocsp_uri.hostname, ocsp.port do |http|
+     *       http.post ocsp_uri.path, request.to_der,
+     *                 'content-type' => 'application/ocsp-request'
+     *   end
+     *
+     *   response = OpenSSL::OCSP::Response.new http_response.body
+     *   response_basic = response.basic
+     *
+     * First we check if the response has a valid signature.  Without a valid
+     * signature we cannot trust it.  If you get a failure here you may be
+     * missing a system certificate store or may be missing the intermediate
+     * certificates.
+     *
+     *   store = OpenSSL::X509::Store.new
+     *   store.set_default_paths
+     *
+     *   unless response_basic.verify [], store then
+     *     raise 'response is not signed by a trusted certificate'
+     *   end
+     *
+     * The response contains the status information (success/fail).  We can
+     * display the status as a string:
+     *
+     *   puts response.status_string #=> successful
+     *
+     * Next we need to know the response details to determine if the response
+     * matches our request.  First we check the nonce.  Again, not all CAs
+     * support a nonce.  See Request#check_nonce for the meanings of the
+     * return values.
+     *
+     *   p request.check_nonce basic_response #=> value from -1 to 3
+     *
+     * Then extract the status information for the certificate from the basic
+     * response.
+     *
+     *   single_response = basic_response.find_response(certificate_id)
+     *
+     *   unless single_response
+     *     raise 'basic_response does not have the status for the certificiate'
+     *   end
+     *
+     * Then check the validity. A status issued in the future must be rejected.
+     *
+     *   unless single_response.check_validity
+     *     raise 'this_update is in the future or next_update time has passed'
+     *   end
+     *
+     *   case single_response.cert_status
+     *   when OpenSSL::OCSP::V_CERTSTATUS_GOOD
+     *     puts 'certificate is still valid'
+     *   when OpenSSL::OCSP::V_CERTSTATUS_REVOKED
+     *     puts "certificate has been revoked at #{single_response.revocation_time}"
+     *   when OpenSSL::OCSP::V_CERTSTATUS_UNKNOWN
+     *     puts 'responder doesn't know about the certificate'
+     *   end
+     */
+
+    mOCSP = rb_define_module_under(mOSSL, "OCSP");
+
+    /*
+     * OCSP error class.
+     */
+
+    eOCSPError = rb_define_class_under(mOCSP, "OCSPError", eOSSLError);
+
+    /*
+     * An OpenSSL::OCSP::Request contains the certificate information for
+     * determining if a certificate has been revoked or not.  A Request can be
+     * created for a certificate or from a DER-encoded request created
+     * elsewhere.
+     */
+
+    cOCSPReq = rb_define_class_under(mOCSP, "Request", rb_cObject);
+    rb_define_alloc_func(cOCSPReq, ossl_ocspreq_alloc);
+    rb_define_copy_func(cOCSPReq, ossl_ocspreq_initialize_copy);
+    rb_define_method(cOCSPReq, "initialize", ossl_ocspreq_initialize, -1);
+    rb_define_method(cOCSPReq, "add_nonce", ossl_ocspreq_add_nonce, -1);
+    rb_define_method(cOCSPReq, "check_nonce", ossl_ocspreq_check_nonce, 1);
+    rb_define_method(cOCSPReq, "add_certid", ossl_ocspreq_add_certid, 1);
+    rb_define_method(cOCSPReq, "certid", ossl_ocspreq_get_certid, 0);
+    rb_define_method(cOCSPReq, "sign", ossl_ocspreq_sign, -1);
+    rb_define_method(cOCSPReq, "verify", ossl_ocspreq_verify, -1);
+    rb_define_method(cOCSPReq, "to_der", ossl_ocspreq_to_der, 0);
+
+    /*
+     * An OpenSSL::OCSP::Response contains the status of a certificate check
+     * which is created from an OpenSSL::OCSP::Request.
+     */
+
+    cOCSPRes = rb_define_class_under(mOCSP, "Response", rb_cObject);
+    rb_define_singleton_method(cOCSPRes, "create", ossl_ocspres_s_create, 2);
+    rb_define_alloc_func(cOCSPRes, ossl_ocspres_alloc);
+    rb_define_copy_func(cOCSPRes, ossl_ocspres_initialize_copy);
+    rb_define_method(cOCSPRes, "initialize", ossl_ocspres_initialize, -1);
+    rb_define_method(cOCSPRes, "status", ossl_ocspres_status, 0);
+    rb_define_method(cOCSPRes, "status_string", ossl_ocspres_status_string, 0);
+    rb_define_method(cOCSPRes, "basic", ossl_ocspres_get_basic, 0);
+    rb_define_method(cOCSPRes, "to_der", ossl_ocspres_to_der, 0);
+
+    /*
+     * An OpenSSL::OCSP::BasicResponse contains the status of a certificate
+     * check which is created from an OpenSSL::OCSP::Request.  A
+     * BasicResponse is more detailed than a Response.
+     */
+
+    cOCSPBasicRes = rb_define_class_under(mOCSP, "BasicResponse", rb_cObject);
+    rb_define_alloc_func(cOCSPBasicRes, ossl_ocspbres_alloc);
+    rb_define_copy_func(cOCSPBasicRes, ossl_ocspbres_initialize_copy);
+    rb_define_method(cOCSPBasicRes, "initialize", ossl_ocspbres_initialize, -1);
+    rb_define_method(cOCSPBasicRes, "copy_nonce", ossl_ocspbres_copy_nonce, 1);
+    rb_define_method(cOCSPBasicRes, "add_nonce", ossl_ocspbres_add_nonce, -1);
+    rb_define_method(cOCSPBasicRes, "add_status", ossl_ocspbres_add_status, 7);
+    rb_define_method(cOCSPBasicRes, "status", ossl_ocspbres_get_status, 0);
+    rb_define_method(cOCSPBasicRes, "responses", ossl_ocspbres_get_responses, 0);
+    rb_define_method(cOCSPBasicRes, "find_response", ossl_ocspbres_find_response, 1);
+    rb_define_method(cOCSPBasicRes, "sign", ossl_ocspbres_sign, -1);
+    rb_define_method(cOCSPBasicRes, "verify", ossl_ocspbres_verify, -1);
+    rb_define_method(cOCSPBasicRes, "to_der", ossl_ocspbres_to_der, 0);
+
+    /*
+     * An OpenSSL::OCSP::SingleResponse represents an OCSP SingleResponse
+     * structure, which contains the basic information of the status of the
+     * certificate.
+     */
+    cOCSPSingleRes = rb_define_class_under(mOCSP, "SingleResponse", rb_cObject);
+    rb_define_alloc_func(cOCSPSingleRes, ossl_ocspsres_alloc);
+    rb_define_copy_func(cOCSPSingleRes, ossl_ocspsres_initialize_copy);
+    rb_define_method(cOCSPSingleRes, "initialize", ossl_ocspsres_initialize, 1);
+    rb_define_method(cOCSPSingleRes, "check_validity", ossl_ocspsres_check_validity, -1);
+    rb_define_method(cOCSPSingleRes, "certid", ossl_ocspsres_get_certid, 0);
+    rb_define_method(cOCSPSingleRes, "cert_status", ossl_ocspsres_get_cert_status, 0);
+    rb_define_method(cOCSPSingleRes, "this_update", ossl_ocspsres_get_this_update, 0);
+    rb_define_method(cOCSPSingleRes, "next_update", ossl_ocspsres_get_next_update, 0);
+    rb_define_method(cOCSPSingleRes, "revocation_time", ossl_ocspsres_get_revocation_time, 0);
+    rb_define_method(cOCSPSingleRes, "revocation_reason", ossl_ocspsres_get_revocation_reason, 0);
+    rb_define_method(cOCSPSingleRes, "extensions", ossl_ocspsres_get_extensions, 0);
+    rb_define_method(cOCSPSingleRes, "to_der", ossl_ocspsres_to_der, 0);
+
+    /*
+     * An OpenSSL::OCSP::CertificateId identifies a certificate to the CA so
+     * that a status check can be performed.
+     */
+
+    cOCSPCertId = rb_define_class_under(mOCSP, "CertificateId", rb_cObject);
+    rb_define_alloc_func(cOCSPCertId, ossl_ocspcid_alloc);
+    rb_define_copy_func(cOCSPCertId, ossl_ocspcid_initialize_copy);
+    rb_define_method(cOCSPCertId, "initialize", ossl_ocspcid_initialize, -1);
+    rb_define_method(cOCSPCertId, "cmp", ossl_ocspcid_cmp, 1);
+    rb_define_method(cOCSPCertId, "cmp_issuer", ossl_ocspcid_cmp_issuer, 1);
+    rb_define_method(cOCSPCertId, "serial", ossl_ocspcid_get_serial, 0);
+    rb_define_method(cOCSPCertId, "issuer_name_hash", ossl_ocspcid_get_issuer_name_hash, 0);
+    rb_define_method(cOCSPCertId, "issuer_key_hash", ossl_ocspcid_get_issuer_key_hash, 0);
+    rb_define_method(cOCSPCertId, "hash_algorithm", ossl_ocspcid_get_hash_algorithm, 0);
+    rb_define_method(cOCSPCertId, "to_der", ossl_ocspcid_to_der, 0);
+
+    /* Internal error in issuer */
+    rb_define_const(mOCSP, "RESPONSE_STATUS_INTERNALERROR", INT2NUM(OCSP_RESPONSE_STATUS_INTERNALERROR));
+
+    /* Illegal confirmation request */
+    rb_define_const(mOCSP, "RESPONSE_STATUS_MALFORMEDREQUEST", INT2NUM(OCSP_RESPONSE_STATUS_MALFORMEDREQUEST));
+
+    /* The certificate was revoked for an unknown reason */
+    rb_define_const(mOCSP, "REVOKED_STATUS_NOSTATUS", INT2NUM(OCSP_REVOKED_STATUS_NOSTATUS));
+
+    /* You must sign the request and resubmit */
+    rb_define_const(mOCSP, "RESPONSE_STATUS_SIGREQUIRED", INT2NUM(OCSP_RESPONSE_STATUS_SIGREQUIRED));
+
+    /* Response has valid confirmations */
+    rb_define_const(mOCSP, "RESPONSE_STATUS_SUCCESSFUL", INT2NUM(OCSP_RESPONSE_STATUS_SUCCESSFUL));
+
+    /* Try again later */
+    rb_define_const(mOCSP, "RESPONSE_STATUS_TRYLATER", INT2NUM(OCSP_RESPONSE_STATUS_TRYLATER));
+
+    /* The certificate subject's name or other information changed */
+    rb_define_const(mOCSP, "REVOKED_STATUS_AFFILIATIONCHANGED", INT2NUM(OCSP_REVOKED_STATUS_AFFILIATIONCHANGED));
+
+    /* This CA certificate was revoked due to a key compromise */
+    rb_define_const(mOCSP, "REVOKED_STATUS_CACOMPROMISE", INT2NUM(OCSP_REVOKED_STATUS_CACOMPROMISE));
+
+    /* The certificate is on hold */
+    rb_define_const(mOCSP, "REVOKED_STATUS_CERTIFICATEHOLD", INT2NUM(OCSP_REVOKED_STATUS_CERTIFICATEHOLD));
+
+    /* The certificate is no longer needed */
+    rb_define_const(mOCSP, "REVOKED_STATUS_CESSATIONOFOPERATION", INT2NUM(OCSP_REVOKED_STATUS_CESSATIONOFOPERATION));
+
+    /* The certificate was revoked due to a key compromise */
+    rb_define_const(mOCSP, "REVOKED_STATUS_KEYCOMPROMISE", INT2NUM(OCSP_REVOKED_STATUS_KEYCOMPROMISE));
+
+    /* The certificate was previously on hold and should now be removed from
+     * the CRL */
+    rb_define_const(mOCSP, "REVOKED_STATUS_REMOVEFROMCRL", INT2NUM(OCSP_REVOKED_STATUS_REMOVEFROMCRL));
+
+    /* The certificate was superseded by a new certificate */
+    rb_define_const(mOCSP, "REVOKED_STATUS_SUPERSEDED", INT2NUM(OCSP_REVOKED_STATUS_SUPERSEDED));
+
+    /* Your request is unauthorized. */
+    rb_define_const(mOCSP, "RESPONSE_STATUS_UNAUTHORIZED", INT2NUM(OCSP_RESPONSE_STATUS_UNAUTHORIZED));
+
+    /* The certificate was revoked for an unspecified reason */
+    rb_define_const(mOCSP, "REVOKED_STATUS_UNSPECIFIED", INT2NUM(OCSP_REVOKED_STATUS_UNSPECIFIED));
+
+    /* Do not include certificates in the response */
+    rb_define_const(mOCSP, "NOCERTS", INT2NUM(OCSP_NOCERTS));
+
+    /* Do not search certificates contained in the response for a signer */
+    rb_define_const(mOCSP, "NOINTERN", INT2NUM(OCSP_NOINTERN));
+
+    /* Do not check the signature on the response */
+    rb_define_const(mOCSP, "NOSIGS", INT2NUM(OCSP_NOSIGS));
+
+    /* Do not verify the certificate chain on the response */
+    rb_define_const(mOCSP, "NOCHAIN", INT2NUM(OCSP_NOCHAIN));
+
+    /* Do not verify the response at all */
+    rb_define_const(mOCSP, "NOVERIFY", INT2NUM(OCSP_NOVERIFY));
+
+    /* Do not check trust */
+    rb_define_const(mOCSP, "NOEXPLICIT", INT2NUM(OCSP_NOEXPLICIT));
+
+    /* (This flag is not used by OpenSSL 1.0.1g) */
+    rb_define_const(mOCSP, "NOCASIGN", INT2NUM(OCSP_NOCASIGN));
+
+    /* (This flag is not used by OpenSSL 1.0.1g) */
+    rb_define_const(mOCSP, "NODELEGATED", INT2NUM(OCSP_NODELEGATED));
+
+    /* Do not make additional signing certificate checks */
+    rb_define_const(mOCSP, "NOCHECKS", INT2NUM(OCSP_NOCHECKS));
+
+    /* Do not verify additional certificates */
+    rb_define_const(mOCSP, "TRUSTOTHER", INT2NUM(OCSP_TRUSTOTHER));
+
+    /* Identify the response by signing the certificate key ID */
+    rb_define_const(mOCSP, "RESPID_KEY", INT2NUM(OCSP_RESPID_KEY));
+
+    /* Do not include producedAt time in response */
+    rb_define_const(mOCSP, "NOTIME", INT2NUM(OCSP_NOTIME));
+
+    /* Indicates the certificate is not revoked but does not necessarily mean
+     * the certificate was issued or that this response is within the
+     * certificate's validity interval */
+    rb_define_const(mOCSP, "V_CERTSTATUS_GOOD", INT2NUM(V_OCSP_CERTSTATUS_GOOD));
+    /* Indicates the certificate has been revoked either permanently or
+     * temporarily (on hold). */
+    rb_define_const(mOCSP, "V_CERTSTATUS_REVOKED", INT2NUM(V_OCSP_CERTSTATUS_REVOKED));
+
+    /* Indicates the responder does not know about the certificate being
+     * requested. */
+    rb_define_const(mOCSP, "V_CERTSTATUS_UNKNOWN", INT2NUM(V_OCSP_CERTSTATUS_UNKNOWN));
+
+    /* The responder ID is based on the key name. */
+    rb_define_const(mOCSP, "V_RESPID_NAME", INT2NUM(V_OCSP_RESPID_NAME));
+
+    /* The responder ID is based on the public key. */
+    rb_define_const(mOCSP, "V_RESPID_KEY", INT2NUM(V_OCSP_RESPID_KEY));
+}
+#else
+void
+Init_ossl_ocsp(void)
+{
+}
+#endif
diff --git a/ext/openssl/ossl_ocsp.h b/ext/openssl/ossl_ocsp.h
new file mode 100644
index 0000000..21e2c99
--- /dev/null
+++ b/ext/openssl/ossl_ocsp.h
@@ -0,0 +1,23 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2003  Michal Rokos <m.rokos at sh.cvut.cz>
+ * Copyright (C) 2003  GOTOU Yuuzou <gotoyuzo at notwork.org>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#if !defined(_OSSL_OCSP_H_)
+#define _OSSL_OCSP_H_
+
+#if !defined(OPENSSL_NO_OCSP)
+extern VALUE mOCSP;
+extern VALUE cOPCSReq;
+extern VALUE cOPCSRes;
+extern VALUE cOPCSBasicRes;
+#endif
+
+void Init_ossl_ocsp(void);
+
+#endif /* _OSSL_OCSP_H_ */
diff --git a/ext/openssl/ossl_pkcs12.c b/ext/openssl/ossl_pkcs12.c
new file mode 100644
index 0000000..0b9c781
--- /dev/null
+++ b/ext/openssl/ossl_pkcs12.c
@@ -0,0 +1,261 @@
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#include "ossl.h"
+
+#define NewPKCS12(klass) \
+    TypedData_Wrap_Struct((klass), &ossl_pkcs12_type, 0)
+
+#define SetPKCS12(obj, p12) do { \
+    if(!(p12)) ossl_raise(rb_eRuntimeError, "PKCS12 wasn't initialized."); \
+    RTYPEDDATA_DATA(obj) = (p12); \
+} while (0)
+
+#define GetPKCS12(obj, p12) do { \
+    TypedData_Get_Struct((obj), PKCS12, &ossl_pkcs12_type, (p12)); \
+    if(!(p12)) ossl_raise(rb_eRuntimeError, "PKCS12 wasn't initialized."); \
+} while (0)
+
+#define SafeGetPKCS12(obj, p12) do { \
+    OSSL_Check_Kind((obj), cPKCS12); \
+    GetPKCS12((obj), (p12)); \
+} while (0)
+
+#define ossl_pkcs12_set_key(o,v)      rb_iv_set((o), "@key", (v))
+#define ossl_pkcs12_set_cert(o,v)     rb_iv_set((o), "@certificate", (v))
+#define ossl_pkcs12_set_ca_certs(o,v) rb_iv_set((o), "@ca_certs", (v))
+#define ossl_pkcs12_get_key(o)        rb_iv_get((o), "@key")
+#define ossl_pkcs12_get_cert(o)       rb_iv_get((o), "@certificate")
+#define ossl_pkcs12_get_ca_certs(o)   rb_iv_get((o), "@ca_certs")
+
+/*
+ * Classes
+ */
+VALUE cPKCS12;
+VALUE ePKCS12Error;
+
+/*
+ * Private
+ */
+static void
+ossl_pkcs12_free(void *ptr)
+{
+    PKCS12_free(ptr);
+}
+
+static const rb_data_type_t ossl_pkcs12_type = {
+    "OpenSSL/PKCS12",
+    {
+	0, ossl_pkcs12_free,
+    },
+    0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
+};
+
+static VALUE
+ossl_pkcs12_s_allocate(VALUE klass)
+{
+    PKCS12 *p12;
+    VALUE obj;
+
+    obj = NewPKCS12(klass);
+    if(!(p12 = PKCS12_new())) ossl_raise(ePKCS12Error, NULL);
+    SetPKCS12(obj, p12);
+
+    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]]]]]])
+ *
+ * === Parameters
+ * * +pass+ - string
+ * * +name+ - A string describing the key.
+ * * +key+ - Any PKey.
+ * * +cert+ - A X509::Certificate.
+ *   * The public_key portion of the certificate must contain a valid public key.
+ *   * The not_before and not_after fields must be filled in.
+ * * +ca+ - An optional array of X509::Certificate's.
+ * * +key_pbe+ - string
+ * * +cert_pbe+ - string
+ * * +key_iter+ - integer
+ * * +mac_iter+ - integer
+ * * +keytype+ - An integer representing an MSIE specific extension.
+ *
+ * Any optional arguments may be supplied as nil to preserve the OpenSSL defaults.
+ *
+ * See the OpenSSL documentation for PKCS12_create().
+ */
+static VALUE
+ossl_pkcs12_s_create(int argc, VALUE *argv, VALUE self)
+{
+    VALUE pass, name, pkey, cert, ca, key_nid, cert_nid, key_iter, mac_iter, keytype;
+    VALUE obj;
+    char *passphrase, *friendlyname;
+    EVP_PKEY *key;
+    X509 *x509;
+    STACK_OF(X509) *x509s;
+    int nkey = 0, ncert = 0, kiter = 0, miter = 0, ktype = 0;
+    PKCS12 *p12;
+
+    rb_scan_args(argc, argv, "46", &pass, &name, &pkey, &cert, &ca, &key_nid, &cert_nid, &key_iter, &mac_iter, &keytype);
+    passphrase = NIL_P(pass) ? NULL : StringValueCStr(pass);
+    friendlyname = NIL_P(name) ? NULL : StringValueCStr(name);
+    key = GetPKeyPtr(pkey);
+    x509 = GetX509CertPtr(cert);
+/* TODO: make a VALUE to nid function */
+    if (!NIL_P(key_nid)) {
+        if ((nkey = OBJ_txt2nid(StringValueCStr(key_nid))) == NID_undef)
+	    ossl_raise(rb_eArgError, "Unknown PBE algorithm %"PRIsVALUE, key_nid);
+    }
+    if (!NIL_P(cert_nid)) {
+        if ((ncert = OBJ_txt2nid(StringValueCStr(cert_nid))) == NID_undef)
+	    ossl_raise(rb_eArgError, "Unknown PBE algorithm %"PRIsVALUE, cert_nid);
+    }
+    if (!NIL_P(key_iter))
+        kiter = NUM2INT(key_iter);
+    if (!NIL_P(mac_iter))
+        miter = NUM2INT(mac_iter);
+    if (!NIL_P(keytype))
+        ktype = NUM2INT(keytype);
+
+    obj = NewPKCS12(cPKCS12);
+    x509s = NIL_P(ca) ? NULL : ossl_x509_ary2sk(ca);
+    p12 = PKCS12_create(passphrase, friendlyname, key, x509, x509s,
+                        nkey, ncert, kiter, miter, ktype);
+    sk_X509_pop_free(x509s, X509_free);
+    if(!p12) ossl_raise(ePKCS12Error, NULL);
+    SetPKCS12(obj, p12);
+
+    ossl_pkcs12_set_key(obj, pkey);
+    ossl_pkcs12_set_cert(obj, cert);
+    ossl_pkcs12_set_ca_certs(obj, ca);
+
+    return obj;
+}
+
+/*
+ * call-seq:
+ *    PKCS12.new -> pkcs12
+ *    PKCS12.new(str) -> pkcs12
+ *    PKCS12.new(str, pass) -> pkcs12
+ *
+ * === Parameters
+ * * +str+ - Must be a DER encoded PKCS12 string.
+ * * +pass+ - string
+ */
+static VALUE
+ossl_pkcs12_initialize(int argc, VALUE *argv, VALUE self)
+{
+    BIO *in;
+    VALUE arg, pass, pkey, cert, ca;
+    char *passphrase;
+    EVP_PKEY *key;
+    X509 *x509;
+    STACK_OF(X509) *x509s = NULL;
+    int st = 0;
+    PKCS12 *pkcs = DATA_PTR(self);
+
+    if(rb_scan_args(argc, argv, "02", &arg, &pass) == 0) return self;
+    passphrase = NIL_P(pass) ? NULL : StringValueCStr(pass);
+    in = ossl_obj2bio(arg);
+    d2i_PKCS12_bio(in, &pkcs);
+    DATA_PTR(self) = pkcs;
+    BIO_free(in);
+
+    pkey = cert = ca = Qnil;
+    /* OpenSSL's bug; PKCS12_parse() puts errors even if it succeeds.
+     * Fixed in OpenSSL 1.0.0t, 1.0.1p, 1.0.2d */
+    ERR_set_mark();
+    if(!PKCS12_parse(pkcs, passphrase, &key, &x509, &x509s))
+	ossl_raise(ePKCS12Error, "PKCS12_parse");
+    ERR_pop_to_mark();
+    if (key) {
+	pkey = rb_protect((VALUE (*)(VALUE))ossl_pkey_new, (VALUE)key, &st);
+	if (st) goto err;
+    }
+    if (x509) {
+	cert = rb_protect((VALUE (*)(VALUE))ossl_x509_new, (VALUE)x509, &st);
+	if (st) goto err;
+    }
+    if (x509s) {
+	ca = rb_protect((VALUE (*)(VALUE))ossl_x509_sk2ary, (VALUE)x509s, &st);
+	if (st) goto err;
+    }
+
+  err:
+    X509_free(x509);
+    sk_X509_pop_free(x509s, X509_free);
+    ossl_pkcs12_set_key(self, pkey);
+    ossl_pkcs12_set_cert(self, cert);
+    ossl_pkcs12_set_ca_certs(self, ca);
+    if(st) rb_jump_tag(st);
+
+    return self;
+}
+
+static VALUE
+ossl_pkcs12_to_der(VALUE self)
+{
+    PKCS12 *p12;
+    VALUE str;
+    long len;
+    unsigned char *p;
+
+    GetPKCS12(self, p12);
+    if((len = i2d_PKCS12(p12, NULL)) <= 0)
+	ossl_raise(ePKCS12Error, NULL);
+    str = rb_str_new(0, len);
+    p = (unsigned char *)RSTRING_PTR(str);
+    if(i2d_PKCS12(p12, &p) <= 0)
+	ossl_raise(ePKCS12Error, NULL);
+    ossl_str_adjust(str, p);
+
+    return str;
+}
+
+void
+Init_ossl_pkcs12(void)
+{
+#if 0
+    mOSSL = rb_define_module("OpenSSL");
+    eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError);
+#endif
+
+    /*
+     * Defines a file format commonly used to store private keys with
+     * accompanying public key certificates, protected with a password-based
+     * symmetric key.
+     */
+    cPKCS12 = rb_define_class_under(mOSSL, "PKCS12", rb_cObject);
+    ePKCS12Error = rb_define_class_under(cPKCS12, "PKCS12Error", eOSSLError);
+    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);
+    rb_define_method(cPKCS12, "initialize", ossl_pkcs12_initialize, -1);
+    rb_define_method(cPKCS12, "to_der", ossl_pkcs12_to_der, 0);
+}
diff --git a/ext/openssl/ossl_pkcs12.h b/ext/openssl/ossl_pkcs12.h
new file mode 100644
index 0000000..fe4f15e
--- /dev/null
+++ b/ext/openssl/ossl_pkcs12.h
@@ -0,0 +1,13 @@
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#if !defined(_OSSL_PKCS12_H_)
+#define _OSSL_PKCS12_H_
+
+extern VALUE cPKCS12;
+extern VALUE ePKCS12Error;
+
+void Init_ossl_pkcs12(void);
+
+#endif /* _OSSL_PKCS12_H_ */
diff --git a/ext/openssl/ossl_pkcs5.c b/ext/openssl/ossl_pkcs5.c
new file mode 100644
index 0000000..47c5bfa
--- /dev/null
+++ b/ext/openssl/ossl_pkcs5.c
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2007 Technorama Ltd. <oss-ruby at technorama.net>
+ */
+#include "ossl.h"
+
+VALUE mPKCS5;
+VALUE ePKCS5;
+
+#ifdef HAVE_PKCS5_PBKDF2_HMAC
+/*
+ * call-seq:
+ *    PKCS5.pbkdf2_hmac(pass, salt, iter, keylen, digest) => string
+ *
+ * === Parameters
+ * * +pass+ - string
+ * * +salt+ - string - should be at least 8 bytes long.
+ * * +iter+ - integer - should be greater than 1000.  20000 is better.
+ * * +keylen+ - integer
+ * * +digest+ - a string or OpenSSL::Digest object.
+ *
+ * Available in OpenSSL >= 1.0.0.
+ *
+ * Digests other than SHA1 may not be supported by other cryptography libraries.
+ */
+static VALUE
+ossl_pkcs5_pbkdf2_hmac(VALUE self, VALUE pass, VALUE salt, VALUE iter, VALUE keylen, VALUE digest)
+{
+    VALUE str;
+    const EVP_MD *md;
+    int len = NUM2INT(keylen);
+
+    StringValue(pass);
+    StringValue(salt);
+    md = GetDigestPtr(digest);
+
+    str = rb_str_new(0, len);
+
+    if (PKCS5_PBKDF2_HMAC(RSTRING_PTR(pass), RSTRING_LENINT(pass),
+			  (unsigned char *)RSTRING_PTR(salt), RSTRING_LENINT(salt),
+			  NUM2INT(iter), md, len,
+			  (unsigned char *)RSTRING_PTR(str)) != 1)
+        ossl_raise(ePKCS5, "PKCS5_PBKDF2_HMAC");
+
+    return str;
+}
+#else
+#define ossl_pkcs5_pbkdf2_hmac rb_f_notimplement
+#endif
+
+
+/*
+ * call-seq:
+ *    PKCS5.pbkdf2_hmac_sha1(pass, salt, iter, keylen) => string
+ *
+ * === Parameters
+ * * +pass+ - string
+ * * +salt+ - string - should be at least 8 bytes long.
+ * * +iter+ - integer - should be greater than 1000.  20000 is better.
+ * * +keylen+ - integer
+ *
+ * This method is available in almost any version of OpenSSL.
+ *
+ * Conforms to RFC 2898.
+ */
+static VALUE
+ossl_pkcs5_pbkdf2_hmac_sha1(VALUE self, VALUE pass, VALUE salt, VALUE iter, VALUE keylen)
+{
+    VALUE str;
+    int len = NUM2INT(keylen);
+
+    StringValue(pass);
+    StringValue(salt);
+
+    str = rb_str_new(0, len);
+
+    if (PKCS5_PBKDF2_HMAC_SHA1(RSTRING_PTR(pass), RSTRING_LENINT(pass),
+			       (const unsigned char *)RSTRING_PTR(salt), RSTRING_LENINT(salt), NUM2INT(iter),
+			       len, (unsigned char *)RSTRING_PTR(str)) != 1)
+        ossl_raise(ePKCS5, "PKCS5_PBKDF2_HMAC_SHA1");
+
+    return str;
+}
+
+void
+Init_ossl_pkcs5(void)
+{
+#if 0
+    mOSSL = rb_define_module("OpenSSL");
+    eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError);
+#endif
+
+    /* Document-class: OpenSSL::PKCS5
+     *
+     * Provides password-based encryption functionality based on PKCS#5.
+     * Typically used for securely deriving arbitrary length symmetric keys
+     * to be used with an OpenSSL::Cipher from passwords. Another use case
+     * is for storing passwords: Due to the ability to tweak the effort of
+     * computation by increasing the iteration count, computation can be
+     * slowed down artificially in order to render possible attacks infeasible.
+     *
+     * PKCS5 offers support for PBKDF2 with an OpenSSL::Digest::SHA1-based
+     * HMAC, or an arbitrary Digest if the underlying version of OpenSSL
+     * already supports it (>= 1.0.0).
+     *
+     * === Parameters
+     * ==== Password
+     * Typically an arbitrary String that represents the password to be used
+     * for deriving a key.
+     * ==== Salt
+     * Prevents attacks based on dictionaries of common passwords. It is a
+     * public value that can be safely stored along with the password (e.g.
+     * if PBKDF2 is used for password storage). For maximum security, a fresh,
+     * random salt should be generated for each stored password. According
+     * to PKCS#5, a salt should be at least 8 bytes long.
+     * ==== Iteration Count
+     * Allows to tweak the length that the actual computation will take. The
+     * larger the iteration count, the longer it will take.
+     * ==== Key Length
+     * Specifies the length in bytes of the output that will be generated.
+     * Typically, the key length should be larger than or equal to the output
+     * length of the underlying digest function, otherwise an attacker could
+     * simply try to brute-force the key. According to PKCS#5, security is
+     * limited by the output length of the underlying digest function, i.e.
+     * security is not improved if a key length strictly larger than the
+     * digest output length is chosen. Therefore, when using PKCS5 for
+     * password storage, it suffices to store values equal to the digest
+     * output length, nothing is gained by storing larger values.
+     *
+     * == Examples
+     * === Generating a 128 bit key for a Cipher (e.g. AES)
+     *   pass = "secret"
+     *   salt = OpenSSL::Random.random_bytes(16)
+     *   iter = 20000
+     *   key_len = 16
+     *   key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(pass, salt, iter, key_len)
+     *
+     * === Storing Passwords
+     *   pass = "secret"
+     *   salt = OpenSSL::Random.random_bytes(16) #store this with the generated value
+     *   iter = 20000
+     *   digest = OpenSSL::Digest::SHA256.new
+     *   len = digest.digest_length
+     *   #the final value to be stored
+     *   value = OpenSSL::PKCS5.pbkdf2_hmac(pass, salt, iter, len, digest)
+     *
+     * === Important Note on Checking Passwords
+     * When comparing passwords provided by the user with previously stored
+     * values, a common mistake made is comparing the two values using "==".
+     * Typically, "==" short-circuits on evaluation, and is therefore
+     * vulnerable to timing attacks. The proper way is to use a method that
+     * always takes the same amount of time when comparing two values, thus
+     * not leaking any information to potential attackers. To compare two
+     * values, the following could be used:
+     *   def eql_time_cmp(a, b)
+     *     unless a.length == b.length
+     *       return false
+     *     end
+     *     cmp = b.bytes.to_a
+     *     result = 0
+     *     a.bytes.each_with_index {|c,i|
+     *       result |= c ^ cmp[i]
+     *     }
+     *     result == 0
+     *   end
+     * Please note that the premature return in case of differing lengths
+     * typically does not leak valuable information - when using PKCS#5, the
+     * length of the values to be compared is of fixed size.
+     */
+
+    mPKCS5 = rb_define_module_under(mOSSL, "PKCS5");
+    /* Document-class: OpenSSL::PKCS5::PKCS5Error
+     *
+     * Generic Exception class that is raised if an error occurs during a
+     * computation.
+     */
+    ePKCS5 = rb_define_class_under(mPKCS5, "PKCS5Error", eOSSLError);
+
+    rb_define_module_function(mPKCS5, "pbkdf2_hmac", ossl_pkcs5_pbkdf2_hmac, 5);
+    rb_define_module_function(mPKCS5, "pbkdf2_hmac_sha1", ossl_pkcs5_pbkdf2_hmac_sha1, 4);
+}
diff --git a/ext/openssl/ossl_pkcs5.h b/ext/openssl/ossl_pkcs5.h
new file mode 100644
index 0000000..a3b132b
--- /dev/null
+++ b/ext/openssl/ossl_pkcs5.h
@@ -0,0 +1,6 @@
+#if !defined(_OSSL_PKCS5_H_)
+#define _OSSL_PKCS5_H_
+
+void Init_ossl_pkcs5(void);
+
+#endif /* _OSSL_PKCS5_H_ */
diff --git a/ext/openssl/ossl_pkcs7.c b/ext/openssl/ossl_pkcs7.c
new file mode 100644
index 0000000..4040355
--- /dev/null
+++ b/ext/openssl/ossl_pkcs7.c
@@ -0,0 +1,1125 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2001-2002  Michal Rokos <m.rokos at sh.cvut.cz>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#include "ossl.h"
+
+#define NewPKCS7(klass) \
+    TypedData_Wrap_Struct((klass), &ossl_pkcs7_type, 0)
+#define SetPKCS7(obj, pkcs7) do { \
+    if (!(pkcs7)) { \
+	ossl_raise(rb_eRuntimeError, "PKCS7 wasn't initialized."); \
+    } \
+    RTYPEDDATA_DATA(obj) = (pkcs7); \
+} while (0)
+#define GetPKCS7(obj, pkcs7) do { \
+    TypedData_Get_Struct((obj), PKCS7, &ossl_pkcs7_type, (pkcs7)); \
+    if (!(pkcs7)) { \
+	ossl_raise(rb_eRuntimeError, "PKCS7 wasn't initialized."); \
+    } \
+} while (0)
+#define SafeGetPKCS7(obj, pkcs7) do { \
+    OSSL_Check_Kind((obj), cPKCS7); \
+    GetPKCS7((obj), (pkcs7)); \
+} while (0)
+
+#define NewPKCS7si(klass) \
+    TypedData_Wrap_Struct((klass), &ossl_pkcs7_signer_info_type, 0)
+#define SetPKCS7si(obj, p7si) do { \
+    if (!(p7si)) { \
+	ossl_raise(rb_eRuntimeError, "PKCS7si wasn't initialized."); \
+    } \
+    RTYPEDDATA_DATA(obj) = (p7si); \
+} while (0)
+#define GetPKCS7si(obj, p7si) do { \
+    TypedData_Get_Struct((obj), PKCS7_SIGNER_INFO, &ossl_pkcs7_signer_info_type, (p7si)); \
+    if (!(p7si)) { \
+	ossl_raise(rb_eRuntimeError, "PKCS7si wasn't initialized."); \
+    } \
+} while (0)
+#define SafeGetPKCS7si(obj, p7si) do { \
+    OSSL_Check_Kind((obj), cPKCS7Signer); \
+    GetPKCS7si((obj), (p7si)); \
+} while (0)
+
+#define NewPKCS7ri(klass) \
+    TypedData_Wrap_Struct((klass), &ossl_pkcs7_recip_info_type, 0)
+#define SetPKCS7ri(obj, p7ri) do { \
+    if (!(p7ri)) { \
+	ossl_raise(rb_eRuntimeError, "PKCS7ri wasn't initialized."); \
+    } \
+    RTYPEDDATA_DATA(obj) = (p7ri); \
+} while (0)
+#define GetPKCS7ri(obj, p7ri) do { \
+    TypedData_Get_Struct((obj), PKCS7_RECIP_INFO, &ossl_pkcs7_recip_info_type, (p7ri)); \
+    if (!(p7ri)) { \
+	ossl_raise(rb_eRuntimeError, "PKCS7ri wasn't initialized."); \
+    } \
+} while (0)
+#define SafeGetPKCS7ri(obj, p7ri) do { \
+    OSSL_Check_Kind((obj), cPKCS7Recipient); \
+    GetPKCS7ri((obj), (p7ri)); \
+} while (0)
+
+#define numberof(ary) (int)(sizeof(ary)/sizeof((ary)[0]))
+
+#define ossl_pkcs7_set_data(o,v)       rb_iv_set((o), "@data", (v))
+#define ossl_pkcs7_get_data(o)         rb_iv_get((o), "@data")
+#define ossl_pkcs7_set_err_string(o,v) rb_iv_set((o), "@error_string", (v))
+#define ossl_pkcs7_get_err_string(o)   rb_iv_get((o), "@error_string")
+
+/*
+ * Classes
+ */
+VALUE cPKCS7;
+VALUE cPKCS7Signer;
+VALUE cPKCS7Recipient;
+VALUE ePKCS7Error;
+
+static void
+ossl_pkcs7_free(void *ptr)
+{
+    PKCS7_free(ptr);
+}
+
+static const rb_data_type_t ossl_pkcs7_type = {
+    "OpenSSL/PKCS7",
+    {
+	0, ossl_pkcs7_free,
+    },
+    0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
+};
+
+static void
+ossl_pkcs7_signer_info_free(void *ptr)
+{
+    PKCS7_SIGNER_INFO_free(ptr);
+}
+
+static const rb_data_type_t ossl_pkcs7_signer_info_type = {
+    "OpenSSL/PKCS7/SIGNER_INFO",
+    {
+	0, ossl_pkcs7_signer_info_free,
+    },
+    0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
+};
+
+static void
+ossl_pkcs7_recip_info_free(void *ptr)
+{
+    PKCS7_RECIP_INFO_free(ptr);
+}
+
+static const rb_data_type_t ossl_pkcs7_recip_info_type = {
+    "OpenSSL/PKCS7/RECIP_INFO",
+    {
+	0, ossl_pkcs7_recip_info_free,
+    },
+    0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
+};
+
+/*
+ * Public
+ * (MADE PRIVATE UNTIL SOMEBODY WILL NEED THEM)
+ */
+static PKCS7_SIGNER_INFO *
+ossl_PKCS7_SIGNER_INFO_dup(const PKCS7_SIGNER_INFO *si)
+{
+    return (PKCS7_SIGNER_INFO *)ASN1_dup((i2d_of_void *)i2d_PKCS7_SIGNER_INFO,
+					 (d2i_of_void *)d2i_PKCS7_SIGNER_INFO,
+					 (char *)si);
+}
+
+static PKCS7_RECIP_INFO *
+ossl_PKCS7_RECIP_INFO_dup(const PKCS7_RECIP_INFO *si)
+{
+    return (PKCS7_RECIP_INFO *)ASN1_dup((i2d_of_void *)i2d_PKCS7_RECIP_INFO,
+					(d2i_of_void *)d2i_PKCS7_RECIP_INFO,
+					(char *)si);
+}
+
+static VALUE
+ossl_pkcs7si_new(PKCS7_SIGNER_INFO *p7si)
+{
+    PKCS7_SIGNER_INFO *pkcs7;
+    VALUE obj;
+
+    obj = NewPKCS7si(cPKCS7Signer);
+    pkcs7 = p7si ? ossl_PKCS7_SIGNER_INFO_dup(p7si) : PKCS7_SIGNER_INFO_new();
+    if (!pkcs7) ossl_raise(ePKCS7Error, NULL);
+    SetPKCS7si(obj, pkcs7);
+
+    return obj;
+}
+
+static PKCS7_SIGNER_INFO *
+DupPKCS7SignerPtr(VALUE obj)
+{
+    PKCS7_SIGNER_INFO *p7si, *pkcs7;
+
+    SafeGetPKCS7si(obj, p7si);
+    if (!(pkcs7 = ossl_PKCS7_SIGNER_INFO_dup(p7si))) {
+	ossl_raise(ePKCS7Error, NULL);
+    }
+
+    return pkcs7;
+}
+
+static VALUE
+ossl_pkcs7ri_new(PKCS7_RECIP_INFO *p7ri)
+{
+    PKCS7_RECIP_INFO *pkcs7;
+    VALUE obj;
+
+    obj = NewPKCS7ri(cPKCS7Recipient);
+    pkcs7 = p7ri ? ossl_PKCS7_RECIP_INFO_dup(p7ri) : PKCS7_RECIP_INFO_new();
+    if (!pkcs7) ossl_raise(ePKCS7Error, NULL);
+    SetPKCS7ri(obj, pkcs7);
+
+    return obj;
+}
+
+static PKCS7_RECIP_INFO *
+DupPKCS7RecipientPtr(VALUE obj)
+{
+    PKCS7_RECIP_INFO *p7ri, *pkcs7;
+
+    SafeGetPKCS7ri(obj, p7ri);
+    if (!(pkcs7 = ossl_PKCS7_RECIP_INFO_dup(p7ri))) {
+	ossl_raise(ePKCS7Error, NULL);
+    }
+
+    return pkcs7;
+}
+
+/*
+ * call-seq:
+ *    PKCS7.read_smime(string) => pkcs7
+ */
+static VALUE
+ossl_pkcs7_s_read_smime(VALUE klass, VALUE arg)
+{
+    BIO *in, *out;
+    PKCS7 *pkcs7;
+    VALUE ret, data;
+
+    ret = NewPKCS7(cPKCS7);
+    in = ossl_obj2bio(arg);
+    out = NULL;
+    pkcs7 = SMIME_read_PKCS7(in, &out);
+    BIO_free(in);
+    if(!pkcs7) ossl_raise(ePKCS7Error, NULL);
+    data = out ? ossl_membio2str(out) : Qnil;
+    SetPKCS7(ret, pkcs7);
+    ossl_pkcs7_set_data(ret, data);
+    ossl_pkcs7_set_err_string(ret, Qnil);
+
+    return ret;
+}
+
+/*
+ * call-seq:
+ *    PKCS7.write_smime(pkcs7 [, data [, flags]]) => string
+ */
+static VALUE
+ossl_pkcs7_s_write_smime(int argc, VALUE *argv, VALUE klass)
+{
+    VALUE pkcs7, data, flags;
+    BIO *out, *in;
+    PKCS7 *p7;
+    VALUE str;
+    int flg;
+
+    rb_scan_args(argc, argv, "12", &pkcs7, &data, &flags);
+    flg = NIL_P(flags) ? 0 : NUM2INT(flags);
+    if(NIL_P(data)) data = ossl_pkcs7_get_data(pkcs7);
+    SafeGetPKCS7(pkcs7, p7);
+    if(!NIL_P(data) && PKCS7_is_detached(p7))
+	flg |= PKCS7_DETACHED;
+    in = NIL_P(data) ? NULL : ossl_obj2bio(data);
+    if(!(out = BIO_new(BIO_s_mem()))){
+        BIO_free(in);
+        ossl_raise(ePKCS7Error, NULL);
+    }
+    if(!SMIME_write_PKCS7(out, p7, in, flg)){
+        BIO_free(out);
+        BIO_free(in);
+        ossl_raise(ePKCS7Error, NULL);
+    }
+    BIO_free(in);
+    str = ossl_membio2str(out);
+
+    return str;
+}
+
+/*
+ * call-seq:
+ *    PKCS7.sign(cert, key, data, [, certs [, flags]]) => pkcs7
+ */
+static VALUE
+ossl_pkcs7_s_sign(int argc, VALUE *argv, VALUE klass)
+{
+    VALUE cert, key, data, certs, flags;
+    X509 *x509;
+    EVP_PKEY *pkey;
+    BIO *in;
+    STACK_OF(X509) *x509s;
+    int flg, status = 0;
+    PKCS7 *pkcs7;
+    VALUE ret;
+
+    rb_scan_args(argc, argv, "32", &cert, &key, &data, &certs, &flags);
+    x509 = GetX509CertPtr(cert); /* NO NEED TO DUP */
+    pkey = GetPrivPKeyPtr(key); /* NO NEED TO DUP */
+    flg = NIL_P(flags) ? 0 : NUM2INT(flags);
+    ret = NewPKCS7(cPKCS7);
+    in = ossl_obj2bio(data);
+    if(NIL_P(certs)) x509s = NULL;
+    else{
+	x509s = ossl_protect_x509_ary2sk(certs, &status);
+	if(status){
+	    BIO_free(in);
+	    rb_jump_tag(status);
+	}
+    }
+    if(!(pkcs7 = PKCS7_sign(x509, pkey, x509s, in, flg))){
+	BIO_free(in);
+	sk_X509_pop_free(x509s, X509_free);
+	ossl_raise(ePKCS7Error, NULL);
+    }
+    SetPKCS7(ret, pkcs7);
+    ossl_pkcs7_set_data(ret, data);
+    ossl_pkcs7_set_err_string(ret, Qnil);
+    BIO_free(in);
+    sk_X509_pop_free(x509s, X509_free);
+
+    return ret;
+}
+
+/*
+ * call-seq:
+ *    PKCS7.encrypt(certs, data, [, cipher [, flags]]) => pkcs7
+ */
+static VALUE
+ossl_pkcs7_s_encrypt(int argc, VALUE *argv, VALUE klass)
+{
+    VALUE certs, data, cipher, flags;
+    STACK_OF(X509) *x509s;
+    BIO *in;
+    const EVP_CIPHER *ciph;
+    int flg, status = 0;
+    VALUE ret;
+    PKCS7 *p7;
+
+    rb_scan_args(argc, argv, "22", &certs, &data, &cipher, &flags);
+    if(NIL_P(cipher)){
+#if !defined(OPENSSL_NO_RC2)
+	ciph = EVP_rc2_40_cbc();
+#elif !defined(OPENSSL_NO_DES)
+	ciph = EVP_des_ede3_cbc();
+#elif !defined(OPENSSL_NO_RC2)
+	ciph = EVP_rc2_40_cbc();
+#elif !defined(OPENSSL_NO_AES)
+	ciph = EVP_EVP_aes_128_cbc();
+#else
+	ossl_raise(ePKCS7Error, "Must specify cipher");
+#endif
+
+    }
+    else ciph = GetCipherPtr(cipher); /* NO NEED TO DUP */
+    flg = NIL_P(flags) ? 0 : NUM2INT(flags);
+    ret = NewPKCS7(cPKCS7);
+    in = ossl_obj2bio(data);
+    x509s = ossl_protect_x509_ary2sk(certs, &status);
+    if(status){
+	BIO_free(in);
+	rb_jump_tag(status);
+    }
+    if(!(p7 = PKCS7_encrypt(x509s, in, (EVP_CIPHER*)ciph, flg))){
+	BIO_free(in);
+	sk_X509_pop_free(x509s, X509_free);
+	ossl_raise(ePKCS7Error, NULL);
+    }
+    BIO_free(in);
+    SetPKCS7(ret, p7);
+    ossl_pkcs7_set_data(ret, data);
+    sk_X509_pop_free(x509s, X509_free);
+
+    return ret;
+}
+
+static VALUE
+ossl_pkcs7_alloc(VALUE klass)
+{
+    PKCS7 *pkcs7;
+    VALUE obj;
+
+    obj = NewPKCS7(klass);
+    if (!(pkcs7 = PKCS7_new())) {
+	ossl_raise(ePKCS7Error, NULL);
+    }
+    SetPKCS7(obj, pkcs7);
+
+    return obj;
+}
+
+/*
+ * call-seq:
+ *    PKCS7.new => pkcs7
+ *    PKCS7.new(string) => pkcs7
+ *
+ * Many methods in this class aren't documented.
+ */
+static VALUE
+ossl_pkcs7_initialize(int argc, VALUE *argv, VALUE self)
+{
+    PKCS7 *p7, *pkcs = DATA_PTR(self);
+    BIO *in;
+    VALUE arg;
+
+    if(rb_scan_args(argc, argv, "01", &arg) == 0)
+	return self;
+    arg = ossl_to_der_if_possible(arg);
+    in = ossl_obj2bio(arg);
+    p7 = PEM_read_bio_PKCS7(in, &pkcs, NULL, NULL);
+    if (!p7) {
+	OSSL_BIO_reset(in);
+        p7 = d2i_PKCS7_bio(in, &pkcs);
+	if (!p7) {
+	    BIO_free(in);
+	    PKCS7_free(pkcs);
+	    DATA_PTR(self) = NULL;
+	    ossl_raise(rb_eArgError, "Could not parse the PKCS7");
+	}
+    }
+    DATA_PTR(self) = pkcs;
+    BIO_free(in);
+    ossl_pkcs7_set_data(self, Qnil);
+    ossl_pkcs7_set_err_string(self, Qnil);
+
+    return self;
+}
+
+static VALUE
+ossl_pkcs7_copy(VALUE self, VALUE other)
+{
+    PKCS7 *a, *b, *pkcs7;
+
+    rb_check_frozen(self);
+    if (self == other) return self;
+
+    GetPKCS7(self, a);
+    SafeGetPKCS7(other, b);
+
+    pkcs7 = PKCS7_dup(b);
+    if (!pkcs7) {
+	ossl_raise(ePKCS7Error, NULL);
+    }
+    DATA_PTR(self) = pkcs7;
+    PKCS7_free(a);
+
+    return self;
+}
+
+static int
+ossl_pkcs7_sym2typeid(VALUE sym)
+{
+    int i, ret = Qnil;
+    const char *s;
+    size_t l;
+
+    static const struct {
+        char name[20];
+        int nid;
+    } p7_type_tab[] = {
+        { "signed",             NID_pkcs7_signed },
+        { "data",               NID_pkcs7_data },
+        { "signedAndEnveloped", NID_pkcs7_signedAndEnveloped },
+        { "enveloped",          NID_pkcs7_enveloped },
+        { "encrypted",          NID_pkcs7_encrypted },
+        { "digest",             NID_pkcs7_digest },
+    };
+
+    if (SYMBOL_P(sym)) sym = rb_sym2str(sym);
+    else StringValue(sym);
+    RSTRING_GETMEM(sym, s, l);
+
+    for(i = 0; ; i++){
+	if(i == numberof(p7_type_tab))
+	    ossl_raise(ePKCS7Error, "unknown type \"%"PRIsVALUE"\"", sym);
+	if(strlen(p7_type_tab[i].name) != l) continue;
+	if(strcmp(p7_type_tab[i].name, s) == 0){
+	    ret = p7_type_tab[i].nid;
+	    break;
+	}
+    }
+
+    return ret;
+}
+
+/*
+ * call-seq:
+ *    pkcs7.type = type => type
+ */
+static VALUE
+ossl_pkcs7_set_type(VALUE self, VALUE type)
+{
+    PKCS7 *p7;
+
+    GetPKCS7(self, p7);
+    if(!PKCS7_set_type(p7, ossl_pkcs7_sym2typeid(type)))
+	ossl_raise(ePKCS7Error, NULL);
+
+    return type;
+}
+
+/*
+ * call-seq:
+ *    pkcs7.type => string or nil
+ */
+static VALUE
+ossl_pkcs7_get_type(VALUE self)
+{
+    PKCS7 *p7;
+
+    GetPKCS7(self, p7);
+    if(PKCS7_type_is_signed(p7))
+	return ID2SYM(rb_intern("signed"));
+    if(PKCS7_type_is_encrypted(p7))
+	return ID2SYM(rb_intern("encrypted"));
+    if(PKCS7_type_is_enveloped(p7))
+	return ID2SYM(rb_intern("enveloped"));
+    if(PKCS7_type_is_signedAndEnveloped(p7))
+	return ID2SYM(rb_intern("signedAndEnveloped"));
+    if(PKCS7_type_is_data(p7))
+	return ID2SYM(rb_intern("data"));
+    return Qnil;
+}
+
+static VALUE
+ossl_pkcs7_set_detached(VALUE self, VALUE flag)
+{
+    PKCS7 *p7;
+
+    GetPKCS7(self, p7);
+    if(flag != Qtrue && flag != Qfalse)
+	ossl_raise(ePKCS7Error, "must specify a boolean");
+    if(!PKCS7_set_detached(p7, flag == Qtrue ? 1 : 0))
+	ossl_raise(ePKCS7Error, NULL);
+
+    return flag;
+}
+
+static VALUE
+ossl_pkcs7_get_detached(VALUE self)
+{
+    PKCS7 *p7;
+    GetPKCS7(self, p7);
+    return PKCS7_get_detached(p7) ? Qtrue : Qfalse;
+}
+
+static VALUE
+ossl_pkcs7_detached_p(VALUE self)
+{
+    PKCS7 *p7;
+    GetPKCS7(self, p7);
+    return PKCS7_is_detached(p7) ? Qtrue : Qfalse;
+}
+
+static VALUE
+ossl_pkcs7_set_cipher(VALUE self, VALUE cipher)
+{
+    PKCS7 *pkcs7;
+
+    GetPKCS7(self, pkcs7);
+    if (!PKCS7_set_cipher(pkcs7, GetCipherPtr(cipher))) {
+	ossl_raise(ePKCS7Error, NULL);
+    }
+
+    return cipher;
+}
+
+static VALUE
+ossl_pkcs7_add_signer(VALUE self, VALUE signer)
+{
+    PKCS7 *pkcs7;
+    PKCS7_SIGNER_INFO *p7si;
+
+    p7si = DupPKCS7SignerPtr(signer); /* NEED TO DUP */
+    GetPKCS7(self, pkcs7);
+    if (!PKCS7_add_signer(pkcs7, p7si)) {
+	PKCS7_SIGNER_INFO_free(p7si);
+	ossl_raise(ePKCS7Error, "Could not add signer.");
+    }
+    if (PKCS7_type_is_signed(pkcs7)){
+	PKCS7_add_signed_attribute(p7si, NID_pkcs9_contentType,
+				   V_ASN1_OBJECT, OBJ_nid2obj(NID_pkcs7_data));
+    }
+
+    return self;
+}
+
+static VALUE
+ossl_pkcs7_get_signer(VALUE self)
+{
+    PKCS7 *pkcs7;
+    STACK_OF(PKCS7_SIGNER_INFO) *sk;
+    PKCS7_SIGNER_INFO *si;
+    int num, i;
+    VALUE ary;
+
+    GetPKCS7(self, pkcs7);
+    if (!(sk = PKCS7_get_signer_info(pkcs7))) {
+	OSSL_Debug("OpenSSL::PKCS7#get_signer_info == NULL!");
+	return rb_ary_new();
+    }
+    if ((num = sk_PKCS7_SIGNER_INFO_num(sk)) < 0) {
+	ossl_raise(ePKCS7Error, "Negative number of signers!");
+    }
+    ary = rb_ary_new2(num);
+    for (i=0; i<num; i++) {
+	si = sk_PKCS7_SIGNER_INFO_value(sk, i);
+	rb_ary_push(ary, ossl_pkcs7si_new(si));
+    }
+
+    return ary;
+}
+
+static VALUE
+ossl_pkcs7_add_recipient(VALUE self, VALUE recip)
+{
+    PKCS7 *pkcs7;
+    PKCS7_RECIP_INFO *ri;
+
+    ri = DupPKCS7RecipientPtr(recip); /* NEED TO DUP */
+    GetPKCS7(self, pkcs7);
+    if (!PKCS7_add_recipient_info(pkcs7, ri)) {
+	PKCS7_RECIP_INFO_free(ri);
+	ossl_raise(ePKCS7Error, "Could not add recipient.");
+    }
+
+    return self;
+}
+
+static VALUE
+ossl_pkcs7_get_recipient(VALUE self)
+{
+    PKCS7 *pkcs7;
+    STACK_OF(PKCS7_RECIP_INFO) *sk;
+    PKCS7_RECIP_INFO *si;
+    int num, i;
+    VALUE ary;
+
+    GetPKCS7(self, pkcs7);
+    if (PKCS7_type_is_enveloped(pkcs7))
+	sk = pkcs7->d.enveloped->recipientinfo;
+    else if (PKCS7_type_is_signedAndEnveloped(pkcs7))
+	sk = pkcs7->d.signed_and_enveloped->recipientinfo;
+    else sk = NULL;
+    if (!sk) return rb_ary_new();
+    if ((num = sk_PKCS7_RECIP_INFO_num(sk)) < 0) {
+	ossl_raise(ePKCS7Error, "Negative number of recipient!");
+    }
+    ary = rb_ary_new2(num);
+    for (i=0; i<num; i++) {
+	si = sk_PKCS7_RECIP_INFO_value(sk, i);
+	rb_ary_push(ary, ossl_pkcs7ri_new(si));
+    }
+
+    return ary;
+}
+
+static VALUE
+ossl_pkcs7_add_certificate(VALUE self, VALUE cert)
+{
+    PKCS7 *pkcs7;
+    X509 *x509;
+
+    GetPKCS7(self, pkcs7);
+    x509 = GetX509CertPtr(cert);  /* NO NEED TO DUP */
+    if (!PKCS7_add_certificate(pkcs7, x509)){
+	ossl_raise(ePKCS7Error, NULL);
+    }
+
+    return self;
+}
+
+static STACK_OF(X509) *
+pkcs7_get_certs(VALUE self)
+{
+    PKCS7 *pkcs7;
+    STACK_OF(X509) *certs;
+    int i;
+
+    GetPKCS7(self, pkcs7);
+    i = OBJ_obj2nid(pkcs7->type);
+    switch(i){
+    case NID_pkcs7_signed:
+        certs = pkcs7->d.sign->cert;
+        break;
+    case NID_pkcs7_signedAndEnveloped:
+        certs = pkcs7->d.signed_and_enveloped->cert;
+        break;
+    default:
+        certs = NULL;
+    }
+
+    return certs;
+}
+
+static STACK_OF(X509_CRL) *
+pkcs7_get_crls(VALUE self)
+{
+    PKCS7 *pkcs7;
+    STACK_OF(X509_CRL) *crls;
+    int i;
+
+    GetPKCS7(self, pkcs7);
+    i = OBJ_obj2nid(pkcs7->type);
+    switch(i){
+    case NID_pkcs7_signed:
+        crls = pkcs7->d.sign->crl;
+        break;
+    case NID_pkcs7_signedAndEnveloped:
+        crls = pkcs7->d.signed_and_enveloped->crl;
+        break;
+    default:
+        crls = NULL;
+    }
+
+    return crls;
+}
+
+static VALUE
+ossl_pkcs7_set_certs_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, arg))
+{
+    return ossl_pkcs7_add_certificate(arg, i);
+}
+
+static VALUE
+ossl_pkcs7_set_certificates(VALUE self, VALUE ary)
+{
+    STACK_OF(X509) *certs;
+    X509 *cert;
+
+    certs = pkcs7_get_certs(self);
+    while((cert = sk_X509_pop(certs))) X509_free(cert);
+    rb_block_call(ary, rb_intern("each"), 0, 0, ossl_pkcs7_set_certs_i, self);
+
+    return ary;
+}
+
+static VALUE
+ossl_pkcs7_get_certificates(VALUE self)
+{
+    return ossl_x509_sk2ary(pkcs7_get_certs(self));
+}
+
+static VALUE
+ossl_pkcs7_add_crl(VALUE self, VALUE crl)
+{
+    PKCS7 *pkcs7;
+    X509_CRL *x509crl;
+
+    GetPKCS7(self, pkcs7); /* NO DUP needed! */
+    x509crl = GetX509CRLPtr(crl);
+    if (!PKCS7_add_crl(pkcs7, x509crl)) {
+	ossl_raise(ePKCS7Error, NULL);
+    }
+
+    return self;
+}
+
+static VALUE
+ossl_pkcs7_set_crls_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, arg))
+{
+    return ossl_pkcs7_add_crl(arg, i);
+}
+
+static VALUE
+ossl_pkcs7_set_crls(VALUE self, VALUE ary)
+{
+    STACK_OF(X509_CRL) *crls;
+    X509_CRL *crl;
+
+    crls = pkcs7_get_crls(self);
+    while((crl = sk_X509_CRL_pop(crls))) X509_CRL_free(crl);
+    rb_block_call(ary, rb_intern("each"), 0, 0, ossl_pkcs7_set_crls_i, self);
+
+    return ary;
+}
+
+static VALUE
+ossl_pkcs7_get_crls(VALUE self)
+{
+    return ossl_x509crl_sk2ary(pkcs7_get_crls(self));
+}
+
+static VALUE
+ossl_pkcs7_verify(int argc, VALUE *argv, VALUE self)
+{
+    VALUE certs, store, indata, flags;
+    STACK_OF(X509) *x509s;
+    X509_STORE *x509st;
+    int flg, ok, status = 0;
+    BIO *in, *out;
+    PKCS7 *p7;
+    VALUE data;
+    const char *msg;
+
+    GetPKCS7(self, p7);
+    rb_scan_args(argc, argv, "22", &certs, &store, &indata, &flags);
+    x509st = GetX509StorePtr(store);
+    flg = NIL_P(flags) ? 0 : NUM2INT(flags);
+    if(NIL_P(indata)) indata = ossl_pkcs7_get_data(self);
+    in = NIL_P(indata) ? NULL : ossl_obj2bio(indata);
+    if(NIL_P(certs)) x509s = NULL;
+    else{
+	x509s = ossl_protect_x509_ary2sk(certs, &status);
+	if(status){
+	    BIO_free(in);
+	    rb_jump_tag(status);
+	}
+    }
+    if(!(out = BIO_new(BIO_s_mem()))){
+	BIO_free(in);
+	sk_X509_pop_free(x509s, X509_free);
+	ossl_raise(ePKCS7Error, NULL);
+    }
+    ok = PKCS7_verify(p7, x509s, x509st, in, out, flg);
+    BIO_free(in);
+    sk_X509_pop_free(x509s, X509_free);
+    if (ok < 0) ossl_raise(ePKCS7Error, "PKCS7_verify");
+    msg = ERR_reason_error_string(ERR_peek_error());
+    ossl_pkcs7_set_err_string(self, msg ? rb_str_new2(msg) : Qnil);
+    ossl_clear_error();
+    data = ossl_membio2str(out);
+    ossl_pkcs7_set_data(self, data);
+
+    return (ok == 1) ? Qtrue : Qfalse;
+}
+
+static VALUE
+ossl_pkcs7_decrypt(int argc, VALUE *argv, VALUE self)
+{
+    VALUE pkey, cert, flags;
+    EVP_PKEY *key;
+    X509 *x509;
+    int flg;
+    PKCS7 *p7;
+    BIO *out;
+    VALUE str;
+
+    rb_scan_args(argc, argv, "21", &pkey, &cert, &flags);
+    key = GetPrivPKeyPtr(pkey); /* NO NEED TO DUP */
+    x509 = GetX509CertPtr(cert); /* NO NEED TO DUP */
+    flg = NIL_P(flags) ? 0 : NUM2INT(flags);
+    GetPKCS7(self, p7);
+    if(!(out = BIO_new(BIO_s_mem())))
+	ossl_raise(ePKCS7Error, NULL);
+    if(!PKCS7_decrypt(p7, key, x509, out, flg)){
+	BIO_free(out);
+	ossl_raise(ePKCS7Error, NULL);
+    }
+    str = ossl_membio2str(out); /* out will be free */
+
+    return str;
+}
+
+static VALUE
+ossl_pkcs7_add_data(VALUE self, VALUE data)
+{
+    PKCS7 *pkcs7;
+    BIO *out, *in;
+    char buf[4096];
+    int len;
+
+    GetPKCS7(self, pkcs7);
+    if(PKCS7_type_is_signed(pkcs7)){
+	if(!PKCS7_content_new(pkcs7, NID_pkcs7_data))
+	    ossl_raise(ePKCS7Error, NULL);
+    }
+    in = ossl_obj2bio(data);
+    if(!(out = PKCS7_dataInit(pkcs7, NULL))) goto err;
+    for(;;){
+	if((len = BIO_read(in, buf, sizeof(buf))) <= 0)
+	    break;
+	if(BIO_write(out, buf, len) != len)
+	    goto err;
+    }
+    if(!PKCS7_dataFinal(pkcs7, out)) goto err;
+    ossl_pkcs7_set_data(self, Qnil);
+
+ err:
+    BIO_free_all(out);
+    BIO_free(in);
+    if(ERR_peek_error()){
+	ossl_raise(ePKCS7Error, NULL);
+    }
+
+    return data;
+}
+
+static VALUE
+ossl_pkcs7_to_der(VALUE self)
+{
+    PKCS7 *pkcs7;
+    VALUE str;
+    long len;
+    unsigned char *p;
+
+    GetPKCS7(self, pkcs7);
+    if((len = i2d_PKCS7(pkcs7, NULL)) <= 0)
+	ossl_raise(ePKCS7Error, NULL);
+    str = rb_str_new(0, len);
+    p = (unsigned char *)RSTRING_PTR(str);
+    if(i2d_PKCS7(pkcs7, &p) <= 0)
+	ossl_raise(ePKCS7Error, NULL);
+    ossl_str_adjust(str, p);
+
+    return str;
+}
+
+static VALUE
+ossl_pkcs7_to_pem(VALUE self)
+{
+    PKCS7 *pkcs7;
+    BIO *out;
+    VALUE str;
+
+    GetPKCS7(self, pkcs7);
+    if (!(out = BIO_new(BIO_s_mem()))) {
+	ossl_raise(ePKCS7Error, NULL);
+    }
+    if (!PEM_write_bio_PKCS7(out, pkcs7)) {
+	BIO_free(out);
+	ossl_raise(ePKCS7Error, NULL);
+    }
+    str = ossl_membio2str(out);
+
+    return str;
+}
+
+/*
+ * SIGNER INFO
+ */
+static VALUE
+ossl_pkcs7si_alloc(VALUE klass)
+{
+    PKCS7_SIGNER_INFO *p7si;
+    VALUE obj;
+
+    obj = NewPKCS7si(klass);
+    if (!(p7si = PKCS7_SIGNER_INFO_new())) {
+	ossl_raise(ePKCS7Error, NULL);
+    }
+    SetPKCS7si(obj, p7si);
+
+    return obj;
+}
+
+static VALUE
+ossl_pkcs7si_initialize(VALUE self, VALUE cert, VALUE key, VALUE digest)
+{
+    PKCS7_SIGNER_INFO *p7si;
+    EVP_PKEY *pkey;
+    X509 *x509;
+    const EVP_MD *md;
+
+    pkey = GetPrivPKeyPtr(key); /* NO NEED TO DUP */
+    x509 = GetX509CertPtr(cert); /* NO NEED TO DUP */
+    md = GetDigestPtr(digest);
+    GetPKCS7si(self, p7si);
+    if (!(PKCS7_SIGNER_INFO_set(p7si, x509, pkey, (EVP_MD*)md))) {
+	ossl_raise(ePKCS7Error, NULL);
+    }
+
+    return self;
+}
+
+static VALUE
+ossl_pkcs7si_get_issuer(VALUE self)
+{
+    PKCS7_SIGNER_INFO *p7si;
+
+    GetPKCS7si(self, p7si);
+
+    return ossl_x509name_new(p7si->issuer_and_serial->issuer);
+}
+
+static VALUE
+ossl_pkcs7si_get_serial(VALUE self)
+{
+    PKCS7_SIGNER_INFO *p7si;
+
+    GetPKCS7si(self, p7si);
+
+    return asn1integer_to_num(p7si->issuer_and_serial->serial);
+}
+
+static VALUE
+ossl_pkcs7si_get_signed_time(VALUE self)
+{
+    PKCS7_SIGNER_INFO *p7si;
+    ASN1_TYPE *asn1obj;
+
+    GetPKCS7si(self, p7si);
+
+    if (!(asn1obj = PKCS7_get_signed_attribute(p7si, NID_pkcs9_signingTime))) {
+	ossl_raise(ePKCS7Error, NULL);
+    }
+    if (asn1obj->type == V_ASN1_UTCTIME) {
+	return asn1time_to_time(asn1obj->value.utctime);
+    }
+    /*
+     * OR
+     * ossl_raise(ePKCS7Error, "...");
+     * ?
+     */
+
+    return Qnil;
+}
+
+/*
+ * RECIPIENT INFO
+ */
+static VALUE
+ossl_pkcs7ri_alloc(VALUE klass)
+{
+    PKCS7_RECIP_INFO *p7ri;
+    VALUE obj;
+
+    obj = NewPKCS7ri(klass);
+    if (!(p7ri = PKCS7_RECIP_INFO_new())) {
+	ossl_raise(ePKCS7Error, NULL);
+    }
+    SetPKCS7ri(obj, p7ri);
+
+    return obj;
+}
+
+static VALUE
+ossl_pkcs7ri_initialize(VALUE self, VALUE cert)
+{
+    PKCS7_RECIP_INFO *p7ri;
+    X509 *x509;
+
+    x509 = GetX509CertPtr(cert); /* NO NEED TO DUP */
+    GetPKCS7ri(self, p7ri);
+    if (!PKCS7_RECIP_INFO_set(p7ri, x509)) {
+	ossl_raise(ePKCS7Error, NULL);
+    }
+
+    return self;
+}
+
+static VALUE
+ossl_pkcs7ri_get_issuer(VALUE self)
+{
+    PKCS7_RECIP_INFO *p7ri;
+
+    GetPKCS7ri(self, p7ri);
+
+    return ossl_x509name_new(p7ri->issuer_and_serial->issuer);
+}
+
+static VALUE
+ossl_pkcs7ri_get_serial(VALUE self)
+{
+    PKCS7_RECIP_INFO *p7ri;
+
+    GetPKCS7ri(self, p7ri);
+
+    return asn1integer_to_num(p7ri->issuer_and_serial->serial);
+}
+
+static VALUE
+ossl_pkcs7ri_get_enc_key(VALUE self)
+{
+    PKCS7_RECIP_INFO *p7ri;
+
+    GetPKCS7ri(self, p7ri);
+
+    return asn1str_to_str(p7ri->enc_key);
+}
+
+/*
+ * INIT
+ */
+void
+Init_ossl_pkcs7(void)
+{
+#if 0
+    mOSSL = rb_define_module("OpenSSL");
+    eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError);
+#endif
+
+    cPKCS7 = rb_define_class_under(mOSSL, "PKCS7", rb_cObject);
+    ePKCS7Error = rb_define_class_under(cPKCS7, "PKCS7Error", eOSSLError);
+    rb_define_singleton_method(cPKCS7, "read_smime", ossl_pkcs7_s_read_smime, 1);
+    rb_define_singleton_method(cPKCS7, "write_smime", ossl_pkcs7_s_write_smime, -1);
+    rb_define_singleton_method(cPKCS7, "sign",  ossl_pkcs7_s_sign, -1);
+    rb_define_singleton_method(cPKCS7, "encrypt", ossl_pkcs7_s_encrypt, -1);
+    rb_attr(cPKCS7, rb_intern("data"), 1, 0, Qfalse);
+    rb_attr(cPKCS7, rb_intern("error_string"), 1, 1, Qfalse);
+    rb_define_alloc_func(cPKCS7, ossl_pkcs7_alloc);
+    rb_define_copy_func(cPKCS7, ossl_pkcs7_copy);
+    rb_define_method(cPKCS7, "initialize", ossl_pkcs7_initialize, -1);
+    rb_define_method(cPKCS7, "type=", ossl_pkcs7_set_type, 1);
+    rb_define_method(cPKCS7, "type", ossl_pkcs7_get_type, 0);
+    rb_define_method(cPKCS7, "detached=", ossl_pkcs7_set_detached, 1);
+    rb_define_method(cPKCS7, "detached", ossl_pkcs7_get_detached, 0);
+    rb_define_method(cPKCS7, "detached?", ossl_pkcs7_detached_p, 0);
+    rb_define_method(cPKCS7, "cipher=", ossl_pkcs7_set_cipher, 1);
+    rb_define_method(cPKCS7, "add_signer", ossl_pkcs7_add_signer, 1);
+    rb_define_method(cPKCS7, "signers", ossl_pkcs7_get_signer, 0);
+    rb_define_method(cPKCS7, "add_recipient", ossl_pkcs7_add_recipient, 1);
+    rb_define_method(cPKCS7, "recipients", ossl_pkcs7_get_recipient, 0);
+    rb_define_method(cPKCS7, "add_certificate", ossl_pkcs7_add_certificate, 1);
+    rb_define_method(cPKCS7, "certificates=", ossl_pkcs7_set_certificates, 1);
+    rb_define_method(cPKCS7, "certificates", ossl_pkcs7_get_certificates, 0);
+    rb_define_method(cPKCS7, "add_crl", ossl_pkcs7_add_crl, 1);
+    rb_define_method(cPKCS7, "crls=", ossl_pkcs7_set_crls, 1);
+    rb_define_method(cPKCS7, "crls", ossl_pkcs7_get_crls, 0);
+    rb_define_method(cPKCS7, "add_data", ossl_pkcs7_add_data, 1);
+    rb_define_alias(cPKCS7,  "data=", "add_data");
+    rb_define_method(cPKCS7, "verify", ossl_pkcs7_verify, -1);
+    rb_define_method(cPKCS7, "decrypt", ossl_pkcs7_decrypt, -1);
+    rb_define_method(cPKCS7, "to_pem", ossl_pkcs7_to_pem, 0);
+    rb_define_alias(cPKCS7,  "to_s", "to_pem");
+    rb_define_method(cPKCS7, "to_der", ossl_pkcs7_to_der, 0);
+
+    cPKCS7Signer = rb_define_class_under(cPKCS7, "SignerInfo", rb_cObject);
+    rb_define_const(cPKCS7, "Signer", cPKCS7Signer);
+    rb_define_alloc_func(cPKCS7Signer, ossl_pkcs7si_alloc);
+    rb_define_method(cPKCS7Signer, "initialize", ossl_pkcs7si_initialize,3);
+    rb_define_method(cPKCS7Signer, "issuer", ossl_pkcs7si_get_issuer, 0);
+    rb_define_alias(cPKCS7Signer, "name", "issuer");
+    rb_define_method(cPKCS7Signer, "serial", ossl_pkcs7si_get_serial,0);
+    rb_define_method(cPKCS7Signer,"signed_time",ossl_pkcs7si_get_signed_time,0);
+
+    cPKCS7Recipient = rb_define_class_under(cPKCS7,"RecipientInfo",rb_cObject);
+    rb_define_alloc_func(cPKCS7Recipient, ossl_pkcs7ri_alloc);
+    rb_define_method(cPKCS7Recipient, "initialize", ossl_pkcs7ri_initialize,1);
+    rb_define_method(cPKCS7Recipient, "issuer", ossl_pkcs7ri_get_issuer,0);
+    rb_define_method(cPKCS7Recipient, "serial", ossl_pkcs7ri_get_serial,0);
+    rb_define_method(cPKCS7Recipient, "enc_key", ossl_pkcs7ri_get_enc_key,0);
+
+#define DefPKCS7Const(x) rb_define_const(cPKCS7, #x, INT2NUM(PKCS7_##x))
+
+    DefPKCS7Const(TEXT);
+    DefPKCS7Const(NOCERTS);
+    DefPKCS7Const(NOSIGS);
+    DefPKCS7Const(NOCHAIN);
+    DefPKCS7Const(NOINTERN);
+    DefPKCS7Const(NOVERIFY);
+    DefPKCS7Const(DETACHED);
+    DefPKCS7Const(BINARY);
+    DefPKCS7Const(NOATTR);
+    DefPKCS7Const(NOSMIMECAP);
+}
diff --git a/ext/openssl/ossl_pkcs7.h b/ext/openssl/ossl_pkcs7.h
new file mode 100644
index 0000000..139e00d
--- /dev/null
+++ b/ext/openssl/ossl_pkcs7.h
@@ -0,0 +1,20 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2001-2002  Michal Rokos <m.rokos at sh.cvut.cz>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#if !defined(_OSSL_PKCS7_H_)
+#define _OSSL_PKCS7_H_
+
+extern VALUE cPKCS7;
+extern VALUE cPKCS7Signer;
+extern VALUE cPKCS7Recipient;
+extern VALUE ePKCS7Error;
+
+void Init_ossl_pkcs7(void);
+
+#endif /* _OSSL_PKCS7_H_ */
diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c
new file mode 100644
index 0000000..6ab1b61
--- /dev/null
+++ b/ext/openssl/ossl_pkey.c
@@ -0,0 +1,483 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2001-2002  Michal Rokos <m.rokos at sh.cvut.cz>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#include "ossl.h"
+
+/*
+ * Classes
+ */
+VALUE mPKey;
+VALUE cPKey;
+VALUE ePKeyError;
+static ID id_private_q;
+
+/*
+ * callback for generating keys
+ */
+int
+ossl_generate_cb_2(int p, int n, BN_GENCB *cb)
+{
+    VALUE ary;
+    struct ossl_generate_cb_arg *arg;
+    int state;
+
+    arg = (struct ossl_generate_cb_arg *)BN_GENCB_get_arg(cb);
+    if (arg->yield) {
+	ary = rb_ary_new2(2);
+	rb_ary_store(ary, 0, INT2NUM(p));
+	rb_ary_store(ary, 1, INT2NUM(n));
+
+	/*
+	* can be break by raising exception or 'break'
+	*/
+	rb_protect(rb_yield, ary, &state);
+	if (state) {
+	    arg->stop = 1;
+	    arg->state = state;
+	}
+    }
+    if (arg->stop) return 0;
+    return 1;
+}
+
+void
+ossl_generate_cb_stop(void *ptr)
+{
+    struct ossl_generate_cb_arg *arg = (struct ossl_generate_cb_arg *)ptr;
+    arg->stop = 1;
+}
+
+static void
+ossl_evp_pkey_free(void *ptr)
+{
+    EVP_PKEY_free(ptr);
+}
+
+/*
+ * Public
+ */
+const rb_data_type_t ossl_evp_pkey_type = {
+    "OpenSSL/EVP_PKEY",
+    {
+	0, ossl_evp_pkey_free,
+    },
+    0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
+};
+
+static VALUE
+pkey_new0(EVP_PKEY *pkey)
+{
+    VALUE obj;
+    int type;
+
+    if (!pkey || (type = EVP_PKEY_base_id(pkey)) == EVP_PKEY_NONE)
+	ossl_raise(rb_eRuntimeError, "pkey is empty");
+
+    switch (type) {
+#if !defined(OPENSSL_NO_RSA)
+    case EVP_PKEY_RSA:
+	return ossl_rsa_new(pkey);
+#endif
+#if !defined(OPENSSL_NO_DSA)
+    case EVP_PKEY_DSA:
+	return ossl_dsa_new(pkey);
+#endif
+#if !defined(OPENSSL_NO_DH)
+    case EVP_PKEY_DH:
+	return ossl_dh_new(pkey);
+#endif
+#if !defined(OPENSSL_NO_EC) && (OPENSSL_VERSION_NUMBER >= 0x0090802fL)
+    case EVP_PKEY_EC:
+	return ossl_ec_new(pkey);
+#endif
+    default:
+	obj = NewPKey(cPKey);
+	SetPKey(obj, pkey);
+	return obj;
+    }
+}
+
+VALUE
+ossl_pkey_new(EVP_PKEY *pkey)
+{
+    VALUE obj;
+    int status;
+
+    obj = rb_protect((VALUE (*)(VALUE))pkey_new0, (VALUE)pkey, &status);
+    if (status) {
+	EVP_PKEY_free(pkey);
+	rb_jump_tag(status);
+    }
+
+    return obj;
+}
+
+/*
+ *  call-seq:
+ *     OpenSSL::PKey.read(string [, pwd ]) -> PKey
+ *     OpenSSL::PKey.read(io [, pwd ]) -> PKey
+ *
+ * Reads a DER or PEM encoded string from +string+ or +io+ and returns an
+ * instance of the appropriate PKey class.
+ *
+ * === Parameters
+ * * +string+ is a DER- or PEM-encoded string containing an arbitrary private
+ *   or public key.
+ * * +io+ is an instance of +IO+ containing a DER- or PEM-encoded
+ *   arbitrary private or public key.
+ * * +pwd+ is an optional password in case +string+ or +file+ is an encrypted
+ *   PEM resource.
+ */
+static VALUE
+ossl_pkey_new_from_data(int argc, VALUE *argv, VALUE self)
+{
+    EVP_PKEY *pkey;
+    BIO *bio;
+    VALUE data, pass;
+
+    rb_scan_args(argc, argv, "11", &data, &pass);
+    pass = ossl_pem_passwd_value(pass);
+
+    bio = ossl_obj2bio(data);
+    if (!(pkey = d2i_PrivateKey_bio(bio, NULL))) {
+	OSSL_BIO_reset(bio);
+	if (!(pkey = PEM_read_bio_PrivateKey(bio, NULL, ossl_pem_passwd_cb, (void *)pass))) {
+	    OSSL_BIO_reset(bio);
+	    if (!(pkey = d2i_PUBKEY_bio(bio, NULL))) {
+		OSSL_BIO_reset(bio);
+		pkey = PEM_read_bio_PUBKEY(bio, NULL, ossl_pem_passwd_cb, (void *)pass);
+	    }
+	}
+    }
+
+    BIO_free(bio);
+    if (!pkey)
+	ossl_raise(ePKeyError, "Could not parse PKey");
+
+    return ossl_pkey_new(pkey);
+}
+
+static void
+pkey_check_public_key(EVP_PKEY *pkey)
+{
+    void *ptr;
+    const BIGNUM *n, *e, *pubkey;
+
+    if (EVP_PKEY_missing_parameters(pkey))
+	ossl_raise(ePKeyError, "parameters missing");
+
+    ptr = EVP_PKEY_get0(pkey);
+    switch (EVP_PKEY_base_id(pkey)) {
+      case EVP_PKEY_RSA:
+	RSA_get0_key(ptr, &n, &e, NULL);
+	if (n && e)
+	    return;
+	break;
+      case EVP_PKEY_DSA:
+	DSA_get0_key(ptr, &pubkey, NULL);
+	if (pubkey)
+	    return;
+	break;
+      case EVP_PKEY_DH:
+	DH_get0_key(ptr, &pubkey, NULL);
+	if (pubkey)
+	    return;
+	break;
+#if !defined(OPENSSL_NO_EC)
+      case EVP_PKEY_EC:
+	if (EC_KEY_get0_public_key(ptr))
+	    return;
+	break;
+#endif
+      default:
+	/* unsupported type; assuming ok */
+	return;
+    }
+    ossl_raise(ePKeyError, "public key missing");
+}
+
+EVP_PKEY *
+GetPKeyPtr(VALUE obj)
+{
+    EVP_PKEY *pkey;
+
+    SafeGetPKey(obj, pkey);
+
+    return pkey;
+}
+
+EVP_PKEY *
+GetPrivPKeyPtr(VALUE obj)
+{
+    EVP_PKEY *pkey;
+
+    if (rb_funcallv(obj, id_private_q, 0, NULL) != Qtrue) {
+	ossl_raise(rb_eArgError, "Private key is needed.");
+    }
+    SafeGetPKey(obj, pkey);
+
+    return pkey;
+}
+
+EVP_PKEY *
+DupPKeyPtr(VALUE obj)
+{
+    EVP_PKEY *pkey;
+
+    SafeGetPKey(obj, pkey);
+    EVP_PKEY_up_ref(pkey);
+
+    return pkey;
+}
+
+/*
+ * Private
+ */
+static VALUE
+ossl_pkey_alloc(VALUE klass)
+{
+    EVP_PKEY *pkey;
+    VALUE obj;
+
+    obj = NewPKey(klass);
+    if (!(pkey = EVP_PKEY_new())) {
+	ossl_raise(ePKeyError, NULL);
+    }
+    SetPKey(obj, pkey);
+
+    return obj;
+}
+
+/*
+ *  call-seq:
+ *      PKeyClass.new -> self
+ *
+ * Because PKey is an abstract class, actually calling this method explicitly
+ * will raise a +NotImplementedError+.
+ */
+static VALUE
+ossl_pkey_initialize(VALUE self)
+{
+    if (rb_obj_is_instance_of(self, cPKey)) {
+	ossl_raise(rb_eTypeError, "OpenSSL::PKey::PKey can't be instantiated directly");
+    }
+    return self;
+}
+
+/*
+ *  call-seq:
+ *      pkey.sign(digest, data) -> String
+ *
+ * To sign the +String+ +data+, +digest+, an instance of OpenSSL::Digest, must
+ * be provided. The return value is again a +String+ containing the signature.
+ * A PKeyError is raised should errors occur.
+ * Any previous state of the +Digest+ instance is irrelevant to the signature
+ * outcome, the digest instance is reset to its initial state during the
+ * operation.
+ *
+ * == Example
+ *   data = 'Sign me!'
+ *   digest = OpenSSL::Digest::SHA256.new
+ *   pkey = OpenSSL::PKey::RSA.new(2048)
+ *   signature = pkey.sign(digest, data)
+ */
+static VALUE
+ossl_pkey_sign(VALUE self, VALUE digest, VALUE data)
+{
+    EVP_PKEY *pkey;
+    const EVP_MD *md;
+    EVP_MD_CTX *ctx;
+    unsigned int buf_len;
+    VALUE str;
+    int result;
+
+    pkey = GetPrivPKeyPtr(self);
+    md = GetDigestPtr(digest);
+    StringValue(data);
+    str = rb_str_new(0, EVP_PKEY_size(pkey));
+
+    ctx = EVP_MD_CTX_new();
+    if (!ctx)
+	ossl_raise(ePKeyError, "EVP_MD_CTX_new");
+    if (!EVP_SignInit_ex(ctx, md, NULL)) {
+	EVP_MD_CTX_free(ctx);
+	ossl_raise(ePKeyError, "EVP_SignInit_ex");
+    }
+    if (!EVP_SignUpdate(ctx, RSTRING_PTR(data), RSTRING_LEN(data))) {
+	EVP_MD_CTX_free(ctx);
+	ossl_raise(ePKeyError, "EVP_SignUpdate");
+    }
+    result = EVP_SignFinal(ctx, (unsigned char *)RSTRING_PTR(str), &buf_len, pkey);
+    EVP_MD_CTX_free(ctx);
+    if (!result)
+	ossl_raise(ePKeyError, "EVP_SignFinal");
+    rb_str_set_len(str, buf_len);
+
+    return str;
+}
+
+/*
+ *  call-seq:
+ *      pkey.verify(digest, signature, data) -> String
+ *
+ * To verify the +String+ +signature+, +digest+, an instance of
+ * OpenSSL::Digest, must be provided to re-compute the message digest of the
+ * original +data+, also a +String+. The return value is +true+ if the
+ * signature is valid, +false+ otherwise. A PKeyError is raised should errors
+ * occur.
+ * Any previous state of the +Digest+ instance is irrelevant to the validation
+ * outcome, the digest instance is reset to its initial state during the
+ * operation.
+ *
+ * == Example
+ *   data = 'Sign me!'
+ *   digest = OpenSSL::Digest::SHA256.new
+ *   pkey = OpenSSL::PKey::RSA.new(2048)
+ *   signature = pkey.sign(digest, data)
+ *   pub_key = pkey.public_key
+ *   puts pub_key.verify(digest, signature, data) # => true
+ */
+static VALUE
+ossl_pkey_verify(VALUE self, VALUE digest, VALUE sig, VALUE data)
+{
+    EVP_PKEY *pkey;
+    const EVP_MD *md;
+    EVP_MD_CTX *ctx;
+    int siglen, result;
+
+    GetPKey(self, pkey);
+    pkey_check_public_key(pkey);
+    md = GetDigestPtr(digest);
+    StringValue(sig);
+    siglen = RSTRING_LENINT(sig);
+    StringValue(data);
+
+    ctx = EVP_MD_CTX_new();
+    if (!ctx)
+	ossl_raise(ePKeyError, "EVP_MD_CTX_new");
+    if (!EVP_VerifyInit_ex(ctx, md, NULL)) {
+	EVP_MD_CTX_free(ctx);
+	ossl_raise(ePKeyError, "EVP_VerifyInit_ex");
+    }
+    if (!EVP_VerifyUpdate(ctx, RSTRING_PTR(data), RSTRING_LEN(data))) {
+	EVP_MD_CTX_free(ctx);
+	ossl_raise(ePKeyError, "EVP_VerifyUpdate");
+    }
+    result = EVP_VerifyFinal(ctx, (unsigned char *)RSTRING_PTR(sig), siglen, pkey);
+    EVP_MD_CTX_free(ctx);
+    switch (result) {
+    case 0:
+	ossl_clear_error();
+	return Qfalse;
+    case 1:
+	return Qtrue;
+    default:
+	ossl_raise(ePKeyError, "EVP_VerifyFinal");
+    }
+}
+
+/*
+ * INIT
+ */
+void
+Init_ossl_pkey(void)
+{
+#if 0
+    mOSSL = rb_define_module("OpenSSL");
+    eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError);
+#endif
+
+    /* Document-module: OpenSSL::PKey
+     *
+     * == Asymmetric Public Key Algorithms
+     *
+     * Asymmetric public key algorithms solve the problem of establishing and
+     * sharing secret keys to en-/decrypt messages. The key in such an
+     * algorithm consists of two parts: a public key that may be distributed
+     * to others and a private key that needs to remain secret.
+     *
+     * Messages encrypted with a public key can only be decrypted by
+     * recipients that are in possession of the associated private key.
+     * Since public key algorithms are considerably slower than symmetric
+     * key algorithms (cf. OpenSSL::Cipher) they are often used to establish
+     * a symmetric key shared between two parties that are in possession of
+     * each other's public key.
+     *
+     * Asymmetric algorithms offer a lot of nice features that are used in a
+     * lot of different areas. A very common application is the creation and
+     * validation of digital signatures. To sign a document, the signatory
+     * generally uses a message digest algorithm (cf. OpenSSL::Digest) to
+     * compute a digest of the document that is then encrypted (i.e. signed)
+     * using the private key. Anyone in possession of the public key may then
+     * verify the signature by computing the message digest of the original
+     * document on their own, decrypting the signature using the signatory's
+     * public key and comparing the result to the message digest they
+     * previously computed. The signature is valid if and only if the
+     * decrypted signature is equal to this message digest.
+     *
+     * The PKey module offers support for three popular public/private key
+     * algorithms:
+     * * RSA (OpenSSL::PKey::RSA)
+     * * DSA (OpenSSL::PKey::DSA)
+     * * Elliptic Curve Cryptography (OpenSSL::PKey::EC)
+     * Each of these implementations is in fact a sub-class of the abstract
+     * PKey class which offers the interface for supporting digital signatures
+     * in the form of PKey#sign and PKey#verify.
+     *
+     * == Diffie-Hellman Key Exchange
+     *
+     * Finally PKey also features OpenSSL::PKey::DH, an implementation of
+     * the Diffie-Hellman key exchange protocol based on discrete logarithms
+     * in finite fields, the same basis that DSA is built on.
+     * The Diffie-Hellman protocol can be used to exchange (symmetric) keys
+     * over insecure channels without needing any prior joint knowledge
+     * between the participating parties. As the security of DH demands
+     * relatively long "public keys" (i.e. the part that is overtly
+     * transmitted between participants) DH tends to be quite slow. If
+     * security or speed is your primary concern, OpenSSL::PKey::EC offers
+     * another implementation of the Diffie-Hellman protocol.
+     *
+     */
+    mPKey = rb_define_module_under(mOSSL, "PKey");
+
+    /* Document-class: OpenSSL::PKey::PKeyError
+     *
+     *Raised when errors occur during PKey#sign or PKey#verify.
+     */
+    ePKeyError = rb_define_class_under(mPKey, "PKeyError", eOSSLError);
+
+    /* Document-class: OpenSSL::PKey::PKey
+     *
+     * An abstract class that bundles signature creation (PKey#sign) and
+     * validation (PKey#verify) that is common to all implementations except
+     * OpenSSL::PKey::DH
+     * * OpenSSL::PKey::RSA
+     * * OpenSSL::PKey::DSA
+     * * OpenSSL::PKey::EC
+     */
+    cPKey = rb_define_class_under(mPKey, "PKey", rb_cObject);
+
+    rb_define_module_function(mPKey, "read", ossl_pkey_new_from_data, -1);
+
+    rb_define_alloc_func(cPKey, ossl_pkey_alloc);
+    rb_define_method(cPKey, "initialize", ossl_pkey_initialize, 0);
+
+    rb_define_method(cPKey, "sign", ossl_pkey_sign, 2);
+    rb_define_method(cPKey, "verify", ossl_pkey_verify, 3);
+
+    id_private_q = rb_intern("private?");
+
+    /*
+     * INIT rsa, dsa, dh, ec
+     */
+    Init_ossl_rsa();
+    Init_ossl_dsa();
+    Init_ossl_dh();
+    Init_ossl_ec();
+}
diff --git a/ext/openssl/ossl_pkey.h b/ext/openssl/ossl_pkey.h
new file mode 100644
index 0000000..e3b723c
--- /dev/null
+++ b/ext/openssl/ossl_pkey.h
@@ -0,0 +1,244 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2001 Michal Rokos <m.rokos at sh.cvut.cz>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#if !defined(_OSSL_PKEY_H_)
+#define _OSSL_PKEY_H_
+
+extern VALUE mPKey;
+extern VALUE cPKey;
+extern VALUE ePKeyError;
+extern const rb_data_type_t ossl_evp_pkey_type;
+
+#define OSSL_PKEY_SET_PRIVATE(obj) rb_iv_set((obj), "private", Qtrue)
+#define OSSL_PKEY_SET_PUBLIC(obj)  rb_iv_set((obj), "private", Qfalse)
+#define OSSL_PKEY_IS_PRIVATE(obj)  (rb_iv_get((obj), "private") == Qtrue)
+
+#define NewPKey(klass) \
+    TypedData_Wrap_Struct((klass), &ossl_evp_pkey_type, 0)
+#define SetPKey(obj, pkey) do { \
+    if (!(pkey)) { \
+	rb_raise(rb_eRuntimeError, "PKEY wasn't initialized!"); \
+    } \
+    RTYPEDDATA_DATA(obj) = (pkey); \
+    OSSL_PKEY_SET_PUBLIC(obj); \
+} while (0)
+#define GetPKey(obj, pkey) do {\
+    TypedData_Get_Struct((obj), EVP_PKEY, &ossl_evp_pkey_type, (pkey)); \
+    if (!(pkey)) { \
+	rb_raise(rb_eRuntimeError, "PKEY wasn't initialized!");\
+    } \
+} while (0)
+#define SafeGetPKey(obj, pkey) do { \
+    OSSL_Check_Kind((obj), cPKey); \
+    GetPKey((obj), (pkey)); \
+} while (0)
+
+struct ossl_generate_cb_arg {
+    int yield;
+    int stop;
+    int state;
+};
+int ossl_generate_cb_2(int p, int n, BN_GENCB *cb);
+void ossl_generate_cb_stop(void *ptr);
+
+VALUE ossl_pkey_new(EVP_PKEY *);
+EVP_PKEY *GetPKeyPtr(VALUE);
+EVP_PKEY *DupPKeyPtr(VALUE);
+EVP_PKEY *GetPrivPKeyPtr(VALUE);
+void Init_ossl_pkey(void);
+
+/*
+ * RSA
+ */
+extern VALUE cRSA;
+extern VALUE eRSAError;
+
+VALUE ossl_rsa_new(EVP_PKEY *);
+void Init_ossl_rsa(void);
+
+/*
+ * DSA
+ */
+extern VALUE cDSA;
+extern VALUE eDSAError;
+
+VALUE ossl_dsa_new(EVP_PKEY *);
+void Init_ossl_dsa(void);
+
+/*
+ * DH
+ */
+extern VALUE cDH;
+extern VALUE eDHError;
+
+VALUE ossl_dh_new(EVP_PKEY *);
+void Init_ossl_dh(void);
+
+/*
+ * EC
+ */
+extern VALUE cEC;
+extern VALUE eECError;
+extern VALUE cEC_GROUP;
+extern VALUE eEC_GROUP;
+extern VALUE cEC_POINT;
+extern VALUE eEC_POINT;
+VALUE ossl_ec_new(EVP_PKEY *);
+void Init_ossl_ec(void);
+
+#define OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, _name, _get)		\
+/*									\
+ *  call-seq:								\
+ *     _keytype##.##_name -> aBN					\
+ */									\
+static VALUE ossl_##_keytype##_get_##_name(VALUE self)			\
+{									\
+	_type *obj;							\
+	const BIGNUM *bn;						\
+									\
+	Get##_type(self, obj);						\
+	_get;								\
+	if (bn == NULL)							\
+		return Qnil;						\
+	return ossl_bn_new(bn);						\
+}
+
+#define OSSL_PKEY_BN_DEF_GETTER3(_keytype, _type, _group, a1, a2, a3)	\
+	OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, a1,			\
+		_type##_get0_##_group(obj, &bn, NULL, NULL))		\
+	OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, a2,			\
+		_type##_get0_##_group(obj, NULL, &bn, NULL))		\
+	OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, a3,			\
+		_type##_get0_##_group(obj, NULL, NULL, &bn))
+
+#define OSSL_PKEY_BN_DEF_GETTER2(_keytype, _type, _group, a1, a2)	\
+	OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, a1,			\
+		_type##_get0_##_group(obj, &bn, NULL))			\
+	OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, a2,			\
+		_type##_get0_##_group(obj, NULL, &bn))
+
+#define OSSL_PKEY_BN_DEF_SETTER3(_keytype, _type, _group, a1, a2, a3)	\
+/*									\
+ *  call-seq:								\
+ *     _keytype##.set_##_group(a1, a2, a3) -> self			\
+ */									\
+static VALUE ossl_##_keytype##_set_##_group(VALUE self, VALUE v1, VALUE v2, VALUE v3) \
+{									\
+	_type *obj;							\
+	BIGNUM *bn1 = NULL, *orig_bn1 = NIL_P(v1) ? NULL : GetBNPtr(v1);\
+	BIGNUM *bn2 = NULL, *orig_bn2 = NIL_P(v2) ? NULL : GetBNPtr(v2);\
+	BIGNUM *bn3 = NULL, *orig_bn3 = NIL_P(v3) ? NULL : GetBNPtr(v3);\
+									\
+	Get##_type(self, obj);						\
+	if (orig_bn1 && !(bn1 = BN_dup(orig_bn1)) ||			\
+	    orig_bn2 && !(bn2 = BN_dup(orig_bn2)) ||			\
+	    orig_bn3 && !(bn3 = BN_dup(orig_bn3))) {			\
+		BN_clear_free(bn1);					\
+		BN_clear_free(bn2);					\
+		BN_clear_free(bn3);					\
+		ossl_raise(eBNError, NULL);				\
+	}								\
+									\
+	if (!_type##_set0_##_group(obj, bn1, bn2, bn3)) {		\
+		BN_clear_free(bn1);					\
+		BN_clear_free(bn2);					\
+		BN_clear_free(bn3);					\
+		ossl_raise(ePKeyError, #_type"_set0_"#_group);		\
+	}								\
+	return self;							\
+}
+
+#define OSSL_PKEY_BN_DEF_SETTER2(_keytype, _type, _group, a1, a2)	\
+/*									\
+ *  call-seq:								\
+ *     _keytype##.set_##_group(a1, a2) -> self				\
+ */									\
+static VALUE ossl_##_keytype##_set_##_group(VALUE self, VALUE v1, VALUE v2) \
+{									\
+	_type *obj;							\
+	BIGNUM *bn1 = NULL, *orig_bn1 = NIL_P(v1) ? NULL : GetBNPtr(v1);\
+	BIGNUM *bn2 = NULL, *orig_bn2 = NIL_P(v2) ? NULL : GetBNPtr(v2);\
+									\
+	Get##_type(self, obj);						\
+	if (orig_bn1 && !(bn1 = BN_dup(orig_bn1)) ||			\
+	    orig_bn2 && !(bn2 = BN_dup(orig_bn2))) {			\
+		BN_clear_free(bn1);					\
+		BN_clear_free(bn2);					\
+		ossl_raise(eBNError, NULL);				\
+	}								\
+									\
+	if (!_type##_set0_##_group(obj, bn1, bn2)) {			\
+		BN_clear_free(bn1);					\
+		BN_clear_free(bn2);					\
+		ossl_raise(ePKeyError, #_type"_set0_"#_group);		\
+	}								\
+	return self;							\
+}
+
+#define OSSL_PKEY_BN_DEF_SETTER_OLD(_keytype, _type, _group, _name)	\
+/*									\
+ *  call-seq:								\
+ *     _keytype##.##_name = bn -> bn					\
+ */									\
+static VALUE ossl_##_keytype##_set_##_name(VALUE self, VALUE bignum)	\
+{									\
+	_type *obj;							\
+	BIGNUM *bn;							\
+									\
+	rb_warning("#"#_name"= is deprecated; use #set_"#_group);	\
+	Get##_type(self, obj);						\
+	if (NIL_P(bignum)) {						\
+		BN_clear_free(obj->_name);				\
+		obj->_name = NULL;					\
+		return Qnil;						\
+	}								\
+									\
+	bn = GetBNPtr(bignum);						\
+	if (obj->_name == NULL)						\
+		obj->_name = BN_new();					\
+	if (obj->_name == NULL)						\
+		ossl_raise(eBNError, NULL);				\
+	if (BN_copy(obj->_name, bn) == NULL)				\
+		ossl_raise(eBNError, NULL);				\
+	return bignum;							\
+}
+
+#if defined(HAVE_OPAQUE_OPENSSL) /* OpenSSL 1.1.0 */
+#define OSSL_PKEY_BN_DEF3(_keytype, _type, _group, a1, a2, a3)		\
+	OSSL_PKEY_BN_DEF_GETTER3(_keytype, _type, _group, a1, a2, a3)	\
+	OSSL_PKEY_BN_DEF_SETTER3(_keytype, _type, _group, a1, a2, a3)
+
+#define OSSL_PKEY_BN_DEF2(_keytype, _type, _group, a1, a2)		\
+	OSSL_PKEY_BN_DEF_GETTER2(_keytype, _type, _group, a1, a2)	\
+	OSSL_PKEY_BN_DEF_SETTER2(_keytype, _type, _group, a1, a2)
+
+#define DEF_OSSL_PKEY_BN(class, keytype, name)				\
+	rb_define_method((class), #name, ossl_##keytype##_get_##name, 0)
+
+#else
+#define OSSL_PKEY_BN_DEF3(_keytype, _type, _group, a1, a2, a3)		\
+	OSSL_PKEY_BN_DEF_GETTER3(_keytype, _type, _group, a1, a2, a3)	\
+	OSSL_PKEY_BN_DEF_SETTER3(_keytype, _type, _group, a1, a2, a3)	\
+	OSSL_PKEY_BN_DEF_SETTER_OLD(_keytype, _type, _group, a1)	\
+	OSSL_PKEY_BN_DEF_SETTER_OLD(_keytype, _type, _group, a2)	\
+	OSSL_PKEY_BN_DEF_SETTER_OLD(_keytype, _type, _group, a3)
+
+#define OSSL_PKEY_BN_DEF2(_keytype, _type, _group, a1, a2)		\
+	OSSL_PKEY_BN_DEF_GETTER2(_keytype, _type, _group, a1, a2)	\
+	OSSL_PKEY_BN_DEF_SETTER2(_keytype, _type, _group, a1, a2)	\
+	OSSL_PKEY_BN_DEF_SETTER_OLD(_keytype, _type, _group, a1)	\
+	OSSL_PKEY_BN_DEF_SETTER_OLD(_keytype, _type, _group, a2)
+
+#define DEF_OSSL_PKEY_BN(class, keytype, name) do {			\
+	rb_define_method((class), #name, ossl_##keytype##_get_##name, 0);\
+	rb_define_method((class), #name "=", ossl_##keytype##_set_##name, 1);\
+} while (0)
+#endif /* HAVE_OPAQUE_OPENSSL */
+
+#endif /* _OSSL_PKEY_H_ */
diff --git a/ext/openssl/ossl_pkey_dh.c b/ext/openssl/ossl_pkey_dh.c
new file mode 100644
index 0000000..dd85b7b
--- /dev/null
+++ b/ext/openssl/ossl_pkey_dh.c
@@ -0,0 +1,650 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2001-2002  Michal Rokos <m.rokos at sh.cvut.cz>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#include "ossl.h"
+
+#if !defined(OPENSSL_NO_DH)
+
+#define GetPKeyDH(obj, pkey) do { \
+    GetPKey((obj), (pkey)); \
+    if (EVP_PKEY_base_id(pkey) != EVP_PKEY_DH) { /* PARANOIA? */ \
+	ossl_raise(rb_eRuntimeError, "THIS IS NOT A DH!") ; \
+    } \
+} while (0)
+#define GetDH(obj, dh) do { \
+    EVP_PKEY *_pkey; \
+    GetPKeyDH((obj), _pkey); \
+    (dh) = EVP_PKEY_get0_DH(_pkey); \
+} while (0)
+
+/*
+ * Classes
+ */
+VALUE cDH;
+VALUE eDHError;
+
+/*
+ * Public
+ */
+static VALUE
+dh_instance(VALUE klass, DH *dh)
+{
+    EVP_PKEY *pkey;
+    VALUE obj;
+
+    if (!dh) {
+	return Qfalse;
+    }
+    obj = NewPKey(klass);
+    if (!(pkey = EVP_PKEY_new())) {
+	return Qfalse;
+    }
+    if (!EVP_PKEY_assign_DH(pkey, dh)) {
+	EVP_PKEY_free(pkey);
+	return Qfalse;
+    }
+    SetPKey(obj, pkey);
+
+    return obj;
+}
+
+VALUE
+ossl_dh_new(EVP_PKEY *pkey)
+{
+    VALUE obj;
+
+    if (!pkey) {
+	obj = dh_instance(cDH, DH_new());
+    } else {
+	obj = NewPKey(cDH);
+	if (EVP_PKEY_base_id(pkey) != EVP_PKEY_DH) {
+	    ossl_raise(rb_eTypeError, "Not a DH key!");
+	}
+	SetPKey(obj, pkey);
+    }
+    if (obj == Qfalse) {
+	ossl_raise(eDHError, NULL);
+    }
+
+    return obj;
+}
+
+/*
+ * Private
+ */
+struct dh_blocking_gen_arg {
+    DH *dh;
+    int size;
+    int gen;
+    BN_GENCB *cb;
+    int result;
+};
+
+static void *
+dh_blocking_gen(void *arg)
+{
+    struct dh_blocking_gen_arg *gen = (struct dh_blocking_gen_arg *)arg;
+    gen->result = DH_generate_parameters_ex(gen->dh, gen->size, gen->gen, gen->cb);
+    return 0;
+}
+
+static DH *
+dh_generate(int size, int gen)
+{
+    struct ossl_generate_cb_arg cb_arg = { 0 };
+    struct dh_blocking_gen_arg gen_arg;
+    DH *dh = DH_new();
+    BN_GENCB *cb = BN_GENCB_new();
+
+    if (!dh || !cb) {
+	DH_free(dh);
+	BN_GENCB_free(cb);
+	return NULL;
+    }
+
+    if (rb_block_given_p())
+	cb_arg.yield = 1;
+    BN_GENCB_set(cb, ossl_generate_cb_2, &cb_arg);
+    gen_arg.dh = dh;
+    gen_arg.size = size;
+    gen_arg.gen = gen;
+    gen_arg.cb = cb;
+    if (cb_arg.yield == 1) {
+	/* we cannot release GVL when callback proc is supplied */
+	dh_blocking_gen(&gen_arg);
+    } else {
+	/* there's a chance to unblock */
+	rb_thread_call_without_gvl(dh_blocking_gen, &gen_arg, ossl_generate_cb_stop, &cb_arg);
+    }
+
+    BN_GENCB_free(cb);
+    if (!gen_arg.result) {
+	DH_free(dh);
+	if (cb_arg.state) {
+	    /* Clear OpenSSL error queue before re-raising. */
+	    ossl_clear_error();
+	    rb_jump_tag(cb_arg.state);
+	}
+	return NULL;
+    }
+
+    if (!DH_generate_key(dh)) {
+        DH_free(dh);
+        return NULL;
+    }
+
+    return dh;
+}
+
+/*
+ *  call-seq:
+ *     DH.generate(size [, generator]) -> dh
+ *
+ * Creates a new DH instance from scratch by generating the private and public
+ * components alike.
+ *
+ * === Parameters
+ * * +size+ is an integer representing the desired key size. Keys smaller than 1024 bits should be considered insecure.
+ * * +generator+ is a small number > 1, typically 2 or 5.
+ *
+ */
+static VALUE
+ossl_dh_s_generate(int argc, VALUE *argv, VALUE klass)
+{
+    DH *dh ;
+    int g = 2;
+    VALUE size, gen, obj;
+
+    if (rb_scan_args(argc, argv, "11", &size, &gen) == 2) {
+	g = NUM2INT(gen);
+    }
+    dh = dh_generate(NUM2INT(size), g);
+    obj = dh_instance(klass, dh);
+    if (obj == Qfalse) {
+	DH_free(dh);
+	ossl_raise(eDHError, NULL);
+    }
+
+    return obj;
+}
+
+/*
+ * call-seq:
+ *   DH.new -> dh
+ *   DH.new(string) -> dh
+ *   DH.new(size [, generator]) -> dh
+ *
+ * Either generates a DH instance from scratch or by reading already existing
+ * DH parameters from +string+. Note that when reading a DH instance from
+ * data that was encoded from a DH instance by using DH#to_pem or DH#to_der
+ * the result will *not* contain a public/private key pair yet. This needs to
+ * be generated using DH#generate_key! first.
+ *
+ * === Parameters
+ * * +size+ is an integer representing the desired key size. Keys smaller than 1024 bits should be considered insecure.
+ * * +generator+ is a small number > 1, typically 2 or 5.
+ * * +string+ contains the DER or PEM encoded key.
+ *
+ * === Examples
+ *  DH.new # -> dh
+ *  DH.new(1024) # -> dh
+ *  DH.new(1024, 5) # -> dh
+ *  #Reading DH parameters
+ *  dh = DH.new(File.read('parameters.pem')) # -> dh, but no public/private key yet
+ *  dh.generate_key! # -> dh with public and private key
+ */
+static VALUE
+ossl_dh_initialize(int argc, VALUE *argv, VALUE self)
+{
+    EVP_PKEY *pkey;
+    DH *dh;
+    int g = 2;
+    BIO *in;
+    VALUE arg, gen;
+
+    GetPKey(self, pkey);
+    if(rb_scan_args(argc, argv, "02", &arg, &gen) == 0) {
+      dh = DH_new();
+    }
+    else if (RB_INTEGER_TYPE_P(arg)) {
+	if (!NIL_P(gen)) {
+	    g = NUM2INT(gen);
+	}
+	if (!(dh = dh_generate(NUM2INT(arg), g))) {
+	    ossl_raise(eDHError, NULL);
+	}
+    }
+    else {
+	arg = ossl_to_der_if_possible(arg);
+	in = ossl_obj2bio(arg);
+	dh = PEM_read_bio_DHparams(in, NULL, NULL, NULL);
+	if (!dh){
+	    OSSL_BIO_reset(in);
+	    dh = d2i_DHparams_bio(in, NULL);
+	}
+	BIO_free(in);
+	if (!dh) {
+	    ossl_raise(eDHError, NULL);
+	}
+    }
+    if (!EVP_PKEY_assign_DH(pkey, dh)) {
+	DH_free(dh);
+	ossl_raise(eDHError, NULL);
+    }
+    return self;
+}
+
+static VALUE
+ossl_dh_initialize_copy(VALUE self, VALUE other)
+{
+    EVP_PKEY *pkey;
+    DH *dh, *dh_other;
+    const BIGNUM *pub, *priv;
+
+    GetPKey(self, pkey);
+    if (EVP_PKEY_base_id(pkey) != EVP_PKEY_NONE)
+	ossl_raise(eDHError, "DH already initialized");
+    GetDH(other, dh_other);
+
+    dh = DHparams_dup(dh_other);
+    if (!dh)
+	ossl_raise(eDHError, "DHparams_dup");
+    EVP_PKEY_assign_DH(pkey, dh);
+
+    DH_get0_key(dh_other, &pub, &priv);
+    if (pub) {
+	BIGNUM *pub2 = BN_dup(pub);
+	BIGNUM *priv2 = BN_dup(priv);
+
+	if (!pub2 || priv && !priv2) {
+	    BN_clear_free(pub2);
+	    BN_clear_free(priv2);
+	    ossl_raise(eDHError, "BN_dup");
+	}
+	DH_set0_key(dh, pub2, priv2);
+    }
+
+    return self;
+}
+
+/*
+ *  call-seq:
+ *     dh.public? -> true | false
+ *
+ * Indicates whether this DH instance has a public key associated with it or
+ * not. The public key may be retrieved with DH#pub_key.
+ */
+static VALUE
+ossl_dh_is_public(VALUE self)
+{
+    DH *dh;
+    const BIGNUM *bn;
+
+    GetDH(self, dh);
+    DH_get0_key(dh, &bn, NULL);
+
+    return bn ? Qtrue : Qfalse;
+}
+
+/*
+ *  call-seq:
+ *     dh.private? -> true | false
+ *
+ * Indicates whether this DH instance has a private key associated with it or
+ * not. The private key may be retrieved with DH#priv_key.
+ */
+static VALUE
+ossl_dh_is_private(VALUE self)
+{
+    DH *dh;
+    const BIGNUM *bn;
+
+    GetDH(self, dh);
+    DH_get0_key(dh, NULL, &bn);
+
+#if !defined(OPENSSL_NO_ENGINE)
+    return (bn || DH_get0_engine(dh)) ? Qtrue : Qfalse;
+#else
+    return bn ? Qtrue : Qfalse;
+#endif
+}
+
+/*
+ *  call-seq:
+ *     dh.export -> aString
+ *     dh.to_pem -> aString
+ *     dh.to_s -> aString
+ *
+ * Encodes this DH to its PEM encoding. Note that any existing per-session
+ * public/private keys will *not* get encoded, just the Diffie-Hellman
+ * parameters will be encoded.
+ */
+static VALUE
+ossl_dh_export(VALUE self)
+{
+    DH *dh;
+    BIO *out;
+    VALUE str;
+
+    GetDH(self, dh);
+    if (!(out = BIO_new(BIO_s_mem()))) {
+	ossl_raise(eDHError, NULL);
+    }
+    if (!PEM_write_bio_DHparams(out, dh)) {
+	BIO_free(out);
+	ossl_raise(eDHError, NULL);
+    }
+    str = ossl_membio2str(out);
+
+    return str;
+}
+
+/*
+ *  call-seq:
+ *     dh.to_der -> aString
+ *
+ * Encodes this DH to its DER encoding. Note that any existing per-session
+ * public/private keys will *not* get encoded, just the Diffie-Hellman
+ * parameters will be encoded.
+
+ */
+static VALUE
+ossl_dh_to_der(VALUE self)
+{
+    DH *dh;
+    unsigned char *p;
+    long len;
+    VALUE str;
+
+    GetDH(self, dh);
+    if((len = i2d_DHparams(dh, NULL)) <= 0)
+	ossl_raise(eDHError, NULL);
+    str = rb_str_new(0, len);
+    p = (unsigned char *)RSTRING_PTR(str);
+    if(i2d_DHparams(dh, &p) < 0)
+	ossl_raise(eDHError, NULL);
+    ossl_str_adjust(str, p);
+
+    return str;
+}
+
+/*
+ *  call-seq:
+ *     dh.params -> hash
+ *
+ * Stores all parameters of key to the hash
+ * INSECURE: PRIVATE INFORMATIONS CAN LEAK OUT!!!
+ * Don't use :-)) (I's up to you)
+ */
+static VALUE
+ossl_dh_get_params(VALUE self)
+{
+    DH *dh;
+    VALUE hash;
+    const BIGNUM *p, *q, *g, *pub_key, *priv_key;
+
+    GetDH(self, dh);
+    DH_get0_pqg(dh, &p, &q, &g);
+    DH_get0_key(dh, &pub_key, &priv_key);
+
+    hash = rb_hash_new();
+    rb_hash_aset(hash, rb_str_new2("p"), ossl_bn_new(p));
+    rb_hash_aset(hash, rb_str_new2("q"), ossl_bn_new(q));
+    rb_hash_aset(hash, rb_str_new2("g"), ossl_bn_new(g));
+    rb_hash_aset(hash, rb_str_new2("pub_key"), ossl_bn_new(pub_key));
+    rb_hash_aset(hash, rb_str_new2("priv_key"), ossl_bn_new(priv_key));
+
+    return hash;
+}
+
+/*
+ *  call-seq:
+ *     dh.to_text -> aString
+ *
+ * Prints all parameters of key to buffer
+ * INSECURE: PRIVATE INFORMATIONS CAN LEAK OUT!!!
+ * Don't use :-)) (I's up to you)
+ */
+static VALUE
+ossl_dh_to_text(VALUE self)
+{
+    DH *dh;
+    BIO *out;
+    VALUE str;
+
+    GetDH(self, dh);
+    if (!(out = BIO_new(BIO_s_mem()))) {
+	ossl_raise(eDHError, NULL);
+    }
+    if (!DHparams_print(out, dh)) {
+	BIO_free(out);
+	ossl_raise(eDHError, NULL);
+    }
+    str = ossl_membio2str(out);
+
+    return str;
+}
+
+/*
+ *  call-seq:
+ *     dh.public_key -> aDH
+ *
+ * Returns a new DH instance that carries just the public information, i.e.
+ * the prime +p+ and the generator +g+, but no public/private key yet. Such
+ * a pair may be generated using DH#generate_key!. The "public key" needed
+ * for a key exchange with DH#compute_key is considered as per-session
+ * information and may be retrieved with DH#pub_key once a key pair has
+ * been generated.
+ * If the current instance already contains private information (and thus a
+ * valid public/private key pair), this information will no longer be present
+ * in the new instance generated by DH#public_key. This feature is helpful for
+ * publishing the Diffie-Hellman parameters without leaking any of the private
+ * per-session information.
+ *
+ * === Example
+ *  dh = OpenSSL::PKey::DH.new(2048) # has public and private key set
+ *  public_key = dh.public_key # contains only prime and generator
+ *  parameters = public_key.to_der # it's safe to publish this
+ */
+static VALUE
+ossl_dh_to_public_key(VALUE self)
+{
+    DH *orig_dh, *dh;
+    VALUE obj;
+
+    GetDH(self, orig_dh);
+    dh = DHparams_dup(orig_dh); /* err check perfomed by dh_instance */
+    obj = dh_instance(rb_obj_class(self), dh);
+    if (obj == Qfalse) {
+	DH_free(dh);
+	ossl_raise(eDHError, NULL);
+    }
+
+    return obj;
+}
+
+/*
+ *  call-seq:
+ *     dh.params_ok? -> true | false
+ *
+ * Validates the Diffie-Hellman parameters associated with this instance.
+ * It checks whether a safe prime and a suitable generator are used. If this
+ * is not the case, +false+ is returned.
+ */
+static VALUE
+ossl_dh_check_params(VALUE self)
+{
+    DH *dh;
+    int codes;
+
+    GetDH(self, dh);
+    if (!DH_check(dh, &codes)) {
+	return Qfalse;
+    }
+
+    return codes == 0 ? Qtrue : Qfalse;
+}
+
+/*
+ *  call-seq:
+ *     dh.generate_key! -> self
+ *
+ * Generates a private and public key unless a private key already exists.
+ * If this DH instance was generated from public DH parameters (e.g. by
+ * encoding the result of DH#public_key), then this method needs to be
+ * called first in order to generate the per-session keys before performing
+ * the actual key exchange.
+ *
+ * === Example
+ *   dh = OpenSSL::PKey::DH.new(2048)
+ *   public_key = dh.public_key #contains no private/public key yet
+ *   public_key.generate_key!
+ *   puts public_key.private? # => true
+ */
+static VALUE
+ossl_dh_generate_key(VALUE self)
+{
+    DH *dh;
+
+    GetDH(self, dh);
+    if (!DH_generate_key(dh))
+	ossl_raise(eDHError, "Failed to generate key");
+    return self;
+}
+
+/*
+ *  call-seq:
+ *     dh.compute_key(pub_bn) -> aString
+ *
+ * Returns a String containing a shared secret computed from the other party's public value.
+ * See DH_compute_key() for further information.
+ *
+ * === Parameters
+ * * +pub_bn+ is a OpenSSL::BN, *not* the DH instance returned by
+ *   DH#public_key as that contains the DH parameters only.
+ */
+static VALUE
+ossl_dh_compute_key(VALUE self, VALUE pub)
+{
+    DH *dh;
+    const BIGNUM *pub_key, *dh_p;
+    VALUE str;
+    int len;
+
+    GetDH(self, dh);
+    DH_get0_pqg(dh, &dh_p, NULL, NULL);
+    if (!dh_p)
+	ossl_raise(eDHError, "incomplete DH");
+    pub_key = GetBNPtr(pub);
+    len = DH_size(dh);
+    str = rb_str_new(0, len);
+    if ((len = DH_compute_key((unsigned char *)RSTRING_PTR(str), pub_key, dh)) < 0) {
+	ossl_raise(eDHError, NULL);
+    }
+    rb_str_set_len(str, len);
+
+    return str;
+}
+
+/*
+ * Document-method: OpenSSL::PKey::DH#set_pqg
+ * call-seq:
+ *   dh.set_pqg(p, q, g) -> self
+ *
+ * Sets +p+, +q+, +g+ for the DH instance.
+ */
+OSSL_PKEY_BN_DEF3(dh, DH, pqg, p, q, g)
+/*
+ * Document-method: OpenSSL::PKey::DH#set_key
+ * call-seq:
+ *   dh.set_key(pub_key, priv_key) -> self
+ *
+ * Sets +pub_key+ and +priv_key+ for the DH instance. +priv_key+ may be nil.
+ */
+OSSL_PKEY_BN_DEF2(dh, DH, key, pub_key, priv_key)
+
+/*
+ * INIT
+ */
+void
+Init_ossl_dh(void)
+{
+#if 0
+    mPKey = rb_define_module_under(mOSSL, "PKey");
+    cPKey = rb_define_class_under(mPKey, "PKey", rb_cObject);
+    ePKeyError = rb_define_class_under(mPKey, "PKeyError", eOSSLError);
+#endif
+
+    /* Document-class: OpenSSL::PKey::DHError
+     *
+     * Generic exception that is raised if an operation on a DH PKey
+     * fails unexpectedly or in case an instantiation of an instance of DH
+     * fails due to non-conformant input data.
+     */
+    eDHError = rb_define_class_under(mPKey, "DHError", ePKeyError);
+    /* Document-class: OpenSSL::PKey::DH
+     *
+     * An implementation of the Diffie-Hellman key exchange protocol based on
+     * discrete logarithms in finite fields, the same basis that DSA is built
+     * on.
+     *
+     * === Accessor methods for the Diffie-Hellman parameters
+     * DH#p::
+     *   The prime (an OpenSSL::BN) of the Diffie-Hellman parameters.
+     * DH#g::
+     *   The generator (an OpenSSL::BN) g of the Diffie-Hellman parameters.
+     * DH#pub_key::
+     *   The per-session public key (an OpenSSL::BN) matching the private key.
+     *   This needs to be passed to DH#compute_key.
+     * DH#priv_key::
+     *   The per-session private key, an OpenSSL::BN.
+     *
+     * === Example of a key exchange
+     *  dh1 = OpenSSL::PKey::DH.new(2048)
+     *  der = dh1.public_key.to_der #you may send this publicly to the participating party
+     *  dh2 = OpenSSL::PKey::DH.new(der)
+     *  dh2.generate_key! #generate the per-session key pair
+     *  symm_key1 = dh1.compute_key(dh2.pub_key)
+     *  symm_key2 = dh2.compute_key(dh1.pub_key)
+     *
+     *  puts symm_key1 == symm_key2 # => true
+     */
+    cDH = rb_define_class_under(mPKey, "DH", cPKey);
+    rb_define_singleton_method(cDH, "generate", ossl_dh_s_generate, -1);
+    rb_define_method(cDH, "initialize", ossl_dh_initialize, -1);
+    rb_define_copy_func(cDH, ossl_dh_initialize_copy);
+    rb_define_method(cDH, "public?", ossl_dh_is_public, 0);
+    rb_define_method(cDH, "private?", ossl_dh_is_private, 0);
+    rb_define_method(cDH, "to_text", ossl_dh_to_text, 0);
+    rb_define_method(cDH, "export", ossl_dh_export, 0);
+    rb_define_alias(cDH, "to_pem", "export");
+    rb_define_alias(cDH, "to_s", "export");
+    rb_define_method(cDH, "to_der", ossl_dh_to_der, 0);
+    rb_define_method(cDH, "public_key", ossl_dh_to_public_key, 0);
+    rb_define_method(cDH, "params_ok?", ossl_dh_check_params, 0);
+    rb_define_method(cDH, "generate_key!", ossl_dh_generate_key, 0);
+    rb_define_method(cDH, "compute_key", ossl_dh_compute_key, 1);
+
+    DEF_OSSL_PKEY_BN(cDH, dh, p);
+    DEF_OSSL_PKEY_BN(cDH, dh, q);
+    DEF_OSSL_PKEY_BN(cDH, dh, g);
+    DEF_OSSL_PKEY_BN(cDH, dh, pub_key);
+    DEF_OSSL_PKEY_BN(cDH, dh, priv_key);
+    rb_define_method(cDH, "set_pqg", ossl_dh_set_pqg, 3);
+    rb_define_method(cDH, "set_key", ossl_dh_set_key, 2);
+
+    rb_define_method(cDH, "params", ossl_dh_get_params, 0);
+}
+
+#else /* defined NO_DH */
+void
+Init_ossl_dh(void)
+{
+}
+#endif /* NO_DH */
diff --git a/ext/openssl/ossl_pkey_dsa.c b/ext/openssl/ossl_pkey_dsa.c
new file mode 100644
index 0000000..8508541
--- /dev/null
+++ b/ext/openssl/ossl_pkey_dsa.c
@@ -0,0 +1,670 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2001-2002  Michal Rokos <m.rokos at sh.cvut.cz>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#include "ossl.h"
+
+#if !defined(OPENSSL_NO_DSA)
+
+#define GetPKeyDSA(obj, pkey) do { \
+    GetPKey((obj), (pkey)); \
+    if (EVP_PKEY_base_id(pkey) != EVP_PKEY_DSA) { /* PARANOIA? */ \
+	ossl_raise(rb_eRuntimeError, "THIS IS NOT A DSA!"); \
+    } \
+} while (0)
+#define GetDSA(obj, dsa) do { \
+    EVP_PKEY *_pkey; \
+    GetPKeyDSA((obj), _pkey); \
+    (dsa) = EVP_PKEY_get0_DSA(_pkey); \
+} while (0)
+
+static inline int
+DSA_HAS_PRIVATE(DSA *dsa)
+{
+    const BIGNUM *bn;
+    DSA_get0_key(dsa, NULL, &bn);
+    return !!bn;
+}
+
+static inline int
+DSA_PRIVATE(VALUE obj, DSA *dsa)
+{
+    return DSA_HAS_PRIVATE(dsa) || OSSL_PKEY_IS_PRIVATE(obj);
+}
+
+/*
+ * Classes
+ */
+VALUE cDSA;
+VALUE eDSAError;
+
+/*
+ * Public
+ */
+static VALUE
+dsa_instance(VALUE klass, DSA *dsa)
+{
+    EVP_PKEY *pkey;
+    VALUE obj;
+
+    if (!dsa) {
+	return Qfalse;
+    }
+    obj = NewPKey(klass);
+    if (!(pkey = EVP_PKEY_new())) {
+	return Qfalse;
+    }
+    if (!EVP_PKEY_assign_DSA(pkey, dsa)) {
+	EVP_PKEY_free(pkey);
+	return Qfalse;
+    }
+    SetPKey(obj, pkey);
+
+    return obj;
+}
+
+VALUE
+ossl_dsa_new(EVP_PKEY *pkey)
+{
+    VALUE obj;
+
+    if (!pkey) {
+	obj = dsa_instance(cDSA, DSA_new());
+    } else {
+	obj = NewPKey(cDSA);
+	if (EVP_PKEY_base_id(pkey) != EVP_PKEY_DSA) {
+	    ossl_raise(rb_eTypeError, "Not a DSA key!");
+	}
+	SetPKey(obj, pkey);
+    }
+    if (obj == Qfalse) {
+	ossl_raise(eDSAError, NULL);
+    }
+
+    return obj;
+}
+
+/*
+ * Private
+ */
+struct dsa_blocking_gen_arg {
+    DSA *dsa;
+    int size;
+    int *counter;
+    unsigned long *h;
+    BN_GENCB *cb;
+    int result;
+};
+
+static void *
+dsa_blocking_gen(void *arg)
+{
+    struct dsa_blocking_gen_arg *gen = (struct dsa_blocking_gen_arg *)arg;
+    gen->result = DSA_generate_parameters_ex(gen->dsa, gen->size, NULL, 0,
+					     gen->counter, gen->h, gen->cb);
+    return 0;
+}
+
+static DSA *
+dsa_generate(int size)
+{
+    struct ossl_generate_cb_arg cb_arg = { 0 };
+    struct dsa_blocking_gen_arg gen_arg;
+    DSA *dsa = DSA_new();
+    BN_GENCB *cb = BN_GENCB_new();
+    int counter;
+    unsigned long h;
+
+    if (!dsa || !cb) {
+	DSA_free(dsa);
+	BN_GENCB_free(cb);
+	return NULL;
+    }
+
+    if (rb_block_given_p())
+	cb_arg.yield = 1;
+    BN_GENCB_set(cb, ossl_generate_cb_2, &cb_arg);
+    gen_arg.dsa = dsa;
+    gen_arg.size = size;
+    gen_arg.counter = &counter;
+    gen_arg.h = &h;
+    gen_arg.cb = cb;
+    if (cb_arg.yield == 1) {
+	/* we cannot release GVL when callback proc is supplied */
+	dsa_blocking_gen(&gen_arg);
+    } else {
+	/* there's a chance to unblock */
+	rb_thread_call_without_gvl(dsa_blocking_gen, &gen_arg, ossl_generate_cb_stop, &cb_arg);
+    }
+
+    BN_GENCB_free(cb);
+    if (!gen_arg.result) {
+	DSA_free(dsa);
+	if (cb_arg.state) {
+	    /* Clear OpenSSL error queue before re-raising. By the way, the
+	     * documentation of DSA_generate_parameters_ex() says the error code
+	     * can be obtained by ERR_get_error(), but the default
+	     * implementation, dsa_builtin_paramgen() doesn't put any error... */
+	    ossl_clear_error();
+	    rb_jump_tag(cb_arg.state);
+	}
+	return NULL;
+    }
+
+    if (!DSA_generate_key(dsa)) {
+	DSA_free(dsa);
+	return NULL;
+    }
+
+    return dsa;
+}
+
+/*
+ *  call-seq:
+ *    DSA.generate(size) -> dsa
+ *
+ * Creates a new DSA instance by generating a private/public key pair
+ * from scratch.
+ *
+ * === Parameters
+ * * +size+ is an integer representing the desired key size.
+ *
+ */
+static VALUE
+ossl_dsa_s_generate(VALUE klass, VALUE size)
+{
+    DSA *dsa = dsa_generate(NUM2INT(size)); /* err handled by dsa_instance */
+    VALUE obj = dsa_instance(klass, dsa);
+
+    if (obj == Qfalse) {
+	DSA_free(dsa);
+	ossl_raise(eDSAError, NULL);
+    }
+
+    return obj;
+}
+
+/*
+ *  call-seq:
+ *    DSA.new -> dsa
+ *    DSA.new(size) -> dsa
+ *    DSA.new(string [, pass]) -> dsa
+ *
+ * Creates a new DSA instance by reading an existing key from +string+.
+ *
+ * === Parameters
+ * * +size+ is an integer representing the desired key size.
+ * * +string+ contains a DER or PEM encoded key.
+ * * +pass+ is a string that contains an optional password.
+ *
+ * === Examples
+ *  DSA.new -> dsa
+ *  DSA.new(1024) -> dsa
+ *  DSA.new(File.read('dsa.pem')) -> dsa
+ *  DSA.new(File.read('dsa.pem'), 'mypassword') -> dsa
+ *
+ */
+static VALUE
+ossl_dsa_initialize(int argc, VALUE *argv, VALUE self)
+{
+    EVP_PKEY *pkey;
+    DSA *dsa;
+    BIO *in;
+    VALUE arg, pass;
+
+    GetPKey(self, pkey);
+    if(rb_scan_args(argc, argv, "02", &arg, &pass) == 0) {
+        dsa = DSA_new();
+    }
+    else if (RB_INTEGER_TYPE_P(arg)) {
+	if (!(dsa = dsa_generate(NUM2INT(arg)))) {
+	    ossl_raise(eDSAError, NULL);
+	}
+    }
+    else {
+	pass = ossl_pem_passwd_value(pass);
+	arg = ossl_to_der_if_possible(arg);
+	in = ossl_obj2bio(arg);
+	dsa = PEM_read_bio_DSAPrivateKey(in, NULL, ossl_pem_passwd_cb, (void *)pass);
+	if (!dsa) {
+	    OSSL_BIO_reset(in);
+	    dsa = PEM_read_bio_DSA_PUBKEY(in, NULL, NULL, NULL);
+	}
+	if (!dsa) {
+	    OSSL_BIO_reset(in);
+	    dsa = d2i_DSAPrivateKey_bio(in, NULL);
+	}
+	if (!dsa) {
+	    OSSL_BIO_reset(in);
+	    dsa = d2i_DSA_PUBKEY_bio(in, NULL);
+	}
+	if (!dsa) {
+	    OSSL_BIO_reset(in);
+#define PEM_read_bio_DSAPublicKey(bp,x,cb,u) (DSA *)PEM_ASN1_read_bio( \
+	(d2i_of_void *)d2i_DSAPublicKey, PEM_STRING_DSA_PUBLIC, (bp), (void **)(x), (cb), (u))
+	    dsa = PEM_read_bio_DSAPublicKey(in, NULL, NULL, NULL);
+#undef PEM_read_bio_DSAPublicKey
+	}
+	BIO_free(in);
+	if (!dsa) {
+	    ossl_clear_error();
+	    ossl_raise(eDSAError, "Neither PUB key nor PRIV key");
+	}
+    }
+    if (!EVP_PKEY_assign_DSA(pkey, dsa)) {
+	DSA_free(dsa);
+	ossl_raise(eDSAError, NULL);
+    }
+
+    return self;
+}
+
+static VALUE
+ossl_dsa_initialize_copy(VALUE self, VALUE other)
+{
+    EVP_PKEY *pkey;
+    DSA *dsa, *dsa_new;
+
+    GetPKey(self, pkey);
+    if (EVP_PKEY_base_id(pkey) != EVP_PKEY_NONE)
+	ossl_raise(eDSAError, "DSA already initialized");
+    GetDSA(other, dsa);
+
+    dsa_new = ASN1_dup((i2d_of_void *)i2d_DSAPrivateKey, (d2i_of_void *)d2i_DSAPrivateKey, (char *)dsa);
+    if (!dsa_new)
+	ossl_raise(eDSAError, "ASN1_dup");
+
+    EVP_PKEY_assign_DSA(pkey, dsa_new);
+
+    return self;
+}
+
+/*
+ *  call-seq:
+ *    dsa.public? -> true | false
+ *
+ * Indicates whether this DSA instance has a public key associated with it or
+ * not. The public key may be retrieved with DSA#public_key.
+ */
+static VALUE
+ossl_dsa_is_public(VALUE self)
+{
+    DSA *dsa;
+    const BIGNUM *bn;
+
+    GetDSA(self, dsa);
+    DSA_get0_key(dsa, &bn, NULL);
+
+    return bn ? Qtrue : Qfalse;
+}
+
+/*
+ *  call-seq:
+ *    dsa.private? -> true | false
+ *
+ * Indicates whether this DSA instance has a private key associated with it or
+ * not. The private key may be retrieved with DSA#private_key.
+ */
+static VALUE
+ossl_dsa_is_private(VALUE self)
+{
+    DSA *dsa;
+
+    GetDSA(self, dsa);
+
+    return DSA_PRIVATE(self, dsa) ? Qtrue : Qfalse;
+}
+
+/*
+ *  call-seq:
+ *    dsa.export([cipher, password]) -> aString
+ *    dsa.to_pem([cipher, password]) -> aString
+ *    dsa.to_s([cipher, password]) -> aString
+ *
+ * Encodes this DSA to its PEM encoding.
+ *
+ * === Parameters
+ * * +cipher+ is an OpenSSL::Cipher.
+ * * +password+ is a string containing your password.
+ *
+ * === Examples
+ *  DSA.to_pem -> aString
+ *  DSA.to_pem(cipher, 'mypassword') -> aString
+ *
+ */
+static VALUE
+ossl_dsa_export(int argc, VALUE *argv, VALUE self)
+{
+    DSA *dsa;
+    BIO *out;
+    const EVP_CIPHER *ciph = NULL;
+    VALUE cipher, pass, str;
+
+    GetDSA(self, dsa);
+    rb_scan_args(argc, argv, "02", &cipher, &pass);
+    if (!NIL_P(cipher)) {
+	ciph = GetCipherPtr(cipher);
+	pass = ossl_pem_passwd_value(pass);
+    }
+    if (!(out = BIO_new(BIO_s_mem()))) {
+	ossl_raise(eDSAError, NULL);
+    }
+    if (DSA_HAS_PRIVATE(dsa)) {
+	if (!PEM_write_bio_DSAPrivateKey(out, dsa, ciph, NULL, 0,
+					 ossl_pem_passwd_cb, (void *)pass)){
+	    BIO_free(out);
+	    ossl_raise(eDSAError, NULL);
+	}
+    } else {
+	if (!PEM_write_bio_DSA_PUBKEY(out, dsa)) {
+	    BIO_free(out);
+	    ossl_raise(eDSAError, NULL);
+	}
+    }
+    str = ossl_membio2str(out);
+
+    return str;
+}
+
+/*
+ *  call-seq:
+ *    dsa.to_der -> aString
+ *
+ * Encodes this DSA to its DER encoding.
+ *
+ */
+static VALUE
+ossl_dsa_to_der(VALUE self)
+{
+    DSA *dsa;
+    int (*i2d_func)(DSA *, unsigned char **);
+    unsigned char *p;
+    long len;
+    VALUE str;
+
+    GetDSA(self, dsa);
+    if(DSA_HAS_PRIVATE(dsa))
+	i2d_func = (int (*)(DSA *,unsigned char **))i2d_DSAPrivateKey;
+    else
+	i2d_func = i2d_DSA_PUBKEY;
+    if((len = i2d_func(dsa, NULL)) <= 0)
+	ossl_raise(eDSAError, NULL);
+    str = rb_str_new(0, len);
+    p = (unsigned char *)RSTRING_PTR(str);
+    if(i2d_func(dsa, &p) < 0)
+	ossl_raise(eDSAError, NULL);
+    ossl_str_adjust(str, p);
+
+    return str;
+}
+
+
+/*
+ *  call-seq:
+ *    dsa.params -> hash
+ *
+ * Stores all parameters of key to the hash
+ * INSECURE: PRIVATE INFORMATIONS CAN LEAK OUT!!!
+ * Don't use :-)) (I's up to you)
+ */
+static VALUE
+ossl_dsa_get_params(VALUE self)
+{
+    DSA *dsa;
+    VALUE hash;
+    const BIGNUM *p, *q, *g, *pub_key, *priv_key;
+
+    GetDSA(self, dsa);
+    DSA_get0_pqg(dsa, &p, &q, &g);
+    DSA_get0_key(dsa, &pub_key, &priv_key);
+
+    hash = rb_hash_new();
+    rb_hash_aset(hash, rb_str_new2("p"), ossl_bn_new(p));
+    rb_hash_aset(hash, rb_str_new2("q"), ossl_bn_new(q));
+    rb_hash_aset(hash, rb_str_new2("g"), ossl_bn_new(g));
+    rb_hash_aset(hash, rb_str_new2("pub_key"), ossl_bn_new(pub_key));
+    rb_hash_aset(hash, rb_str_new2("priv_key"), ossl_bn_new(priv_key));
+
+    return hash;
+}
+
+/*
+ *  call-seq:
+ *    dsa.to_text -> aString
+ *
+ * Prints all parameters of key to buffer
+ * INSECURE: PRIVATE INFORMATIONS CAN LEAK OUT!!!
+ * Don't use :-)) (I's up to you)
+ */
+static VALUE
+ossl_dsa_to_text(VALUE self)
+{
+    DSA *dsa;
+    BIO *out;
+    VALUE str;
+
+    GetDSA(self, dsa);
+    if (!(out = BIO_new(BIO_s_mem()))) {
+	ossl_raise(eDSAError, NULL);
+    }
+    if (!DSA_print(out, dsa, 0)) { /* offset = 0 */
+	BIO_free(out);
+	ossl_raise(eDSAError, NULL);
+    }
+    str = ossl_membio2str(out);
+
+    return str;
+}
+
+/*
+ *  call-seq:
+ *    dsa.public_key -> aDSA
+ *
+ * Returns a new DSA instance that carries just the public key information.
+ * If the current instance has also private key information, this will no
+ * longer be present in the new instance. This feature is helpful for
+ * publishing the public key information without leaking any of the private
+ * information.
+ *
+ * === Example
+ *  dsa = OpenSSL::PKey::DSA.new(2048) # has public and private information
+ *  pub_key = dsa.public_key # has only the public part available
+ *  pub_key_der = pub_key.to_der # it's safe to publish this
+ *
+ *
+ */
+static VALUE
+ossl_dsa_to_public_key(VALUE self)
+{
+    EVP_PKEY *pkey;
+    DSA *dsa;
+    VALUE obj;
+
+    GetPKeyDSA(self, pkey);
+    /* err check performed by dsa_instance */
+#define DSAPublicKey_dup(dsa) (DSA *)ASN1_dup( \
+	(i2d_of_void *)i2d_DSAPublicKey, (d2i_of_void *)d2i_DSAPublicKey, (char *)(dsa))
+    dsa = DSAPublicKey_dup(EVP_PKEY_get0_DSA(pkey));
+#undef DSAPublicKey_dup
+    obj = dsa_instance(rb_obj_class(self), dsa);
+    if (obj == Qfalse) {
+	DSA_free(dsa);
+	ossl_raise(eDSAError, NULL);
+    }
+    return obj;
+}
+
+/*
+ *  call-seq:
+ *    dsa.syssign(string) -> aString
+ *
+ * Computes and returns the DSA signature of +string+, where +string+ is
+ * expected to be an already-computed message digest of the original input
+ * data. The signature is issued using the private key of this DSA instance.
+ *
+ * === Parameters
+ * * +string+ is a message digest of the original input data to be signed
+ *
+ * === Example
+ *  dsa = OpenSSL::PKey::DSA.new(2048)
+ *  doc = "Sign me"
+ *  digest = OpenSSL::Digest::SHA1.digest(doc)
+ *  sig = dsa.syssign(digest)
+ *
+ *
+ */
+static VALUE
+ossl_dsa_sign(VALUE self, VALUE data)
+{
+    DSA *dsa;
+    const BIGNUM *dsa_q;
+    unsigned int buf_len;
+    VALUE str;
+
+    GetDSA(self, dsa);
+    DSA_get0_pqg(dsa, NULL, &dsa_q, NULL);
+    if (!dsa_q)
+	ossl_raise(eDSAError, "incomplete DSA");
+    if (!DSA_PRIVATE(self, dsa))
+	ossl_raise(eDSAError, "Private DSA key needed!");
+    StringValue(data);
+    str = rb_str_new(0, DSA_size(dsa));
+    if (!DSA_sign(0, (unsigned char *)RSTRING_PTR(data), RSTRING_LENINT(data),
+		  (unsigned char *)RSTRING_PTR(str),
+		  &buf_len, dsa)) { /* type is ignored (0) */
+	ossl_raise(eDSAError, NULL);
+    }
+    rb_str_set_len(str, buf_len);
+
+    return str;
+}
+
+/*
+ *  call-seq:
+ *    dsa.sysverify(digest, sig) -> true | false
+ *
+ * Verifies whether the signature is valid given the message digest input. It
+ * does so by validating +sig+ using the public key of this DSA instance.
+ *
+ * === Parameters
+ * * +digest+ is a message digest of the original input data to be signed
+ * * +sig+ is a DSA signature value
+ *
+ * === Example
+ *  dsa = OpenSSL::PKey::DSA.new(2048)
+ *  doc = "Sign me"
+ *  digest = OpenSSL::Digest::SHA1.digest(doc)
+ *  sig = dsa.syssign(digest)
+ *  puts dsa.sysverify(digest, sig) # => true
+ *
+ */
+static VALUE
+ossl_dsa_verify(VALUE self, VALUE digest, VALUE sig)
+{
+    DSA *dsa;
+    int ret;
+
+    GetDSA(self, dsa);
+    StringValue(digest);
+    StringValue(sig);
+    /* type is ignored (0) */
+    ret = DSA_verify(0, (unsigned char *)RSTRING_PTR(digest), RSTRING_LENINT(digest),
+		     (unsigned char *)RSTRING_PTR(sig), RSTRING_LENINT(sig), dsa);
+    if (ret < 0) {
+	ossl_raise(eDSAError, NULL);
+    }
+    else if (ret == 1) {
+	return Qtrue;
+    }
+
+    return Qfalse;
+}
+
+/*
+ * Document-method: OpenSSL::PKey::DSA#set_pqg
+ * call-seq:
+ *   dsa.set_pqg(p, q, g) -> self
+ *
+ * Sets +p+, +q+, +g+ for the DSA instance.
+ */
+OSSL_PKEY_BN_DEF3(dsa, DSA, pqg, p, q, g)
+/*
+ * Document-method: OpenSSL::PKey::DSA#set_key
+ * call-seq:
+ *   dsa.set_key(pub_key, priv_key) -> self
+ *
+ * Sets +pub_key+ and +priv_key+ for the DSA instance. +priv_key+ may be nil.
+ */
+OSSL_PKEY_BN_DEF2(dsa, DSA, key, pub_key, priv_key)
+
+/*
+ * INIT
+ */
+void
+Init_ossl_dsa(void)
+{
+#if 0
+    mPKey = rb_define_module_under(mOSSL, "PKey");
+    cPKey = rb_define_class_under(mPKey, "PKey", rb_cObject);
+    ePKeyError = rb_define_class_under(mPKey, "PKeyError", eOSSLError);
+#endif
+
+    /* Document-class: OpenSSL::PKey::DSAError
+     *
+     * Generic exception that is raised if an operation on a DSA PKey
+     * fails unexpectedly or in case an instantiation of an instance of DSA
+     * fails due to non-conformant input data.
+     */
+    eDSAError = rb_define_class_under(mPKey, "DSAError", ePKeyError);
+
+    /* Document-class: OpenSSL::PKey::DSA
+     *
+     * DSA, the Digital Signature Algorithm, is specified in NIST's
+     * FIPS 186-3. It is an asymmetric public key algorithm that may be used
+     * similar to e.g. RSA.
+     * Please note that for OpenSSL versions prior to 1.0.0 the digest
+     * algorithms OpenSSL::Digest::DSS (equivalent to SHA) or
+     * OpenSSL::Digest::DSS1 (equivalent to SHA-1) must be used for issuing
+     * signatures with a DSA key using OpenSSL::PKey#sign.
+     * Starting with OpenSSL 1.0.0, digest algorithms are no longer restricted,
+     * any Digest may be used for signing.
+     */
+    cDSA = rb_define_class_under(mPKey, "DSA", cPKey);
+
+    rb_define_singleton_method(cDSA, "generate", ossl_dsa_s_generate, 1);
+    rb_define_method(cDSA, "initialize", ossl_dsa_initialize, -1);
+    rb_define_copy_func(cDSA, ossl_dsa_initialize_copy);
+
+    rb_define_method(cDSA, "public?", ossl_dsa_is_public, 0);
+    rb_define_method(cDSA, "private?", ossl_dsa_is_private, 0);
+    rb_define_method(cDSA, "to_text", ossl_dsa_to_text, 0);
+    rb_define_method(cDSA, "export", ossl_dsa_export, -1);
+    rb_define_alias(cDSA, "to_pem", "export");
+    rb_define_alias(cDSA, "to_s", "export");
+    rb_define_method(cDSA, "to_der", ossl_dsa_to_der, 0);
+    rb_define_method(cDSA, "public_key", ossl_dsa_to_public_key, 0);
+    rb_define_method(cDSA, "syssign", ossl_dsa_sign, 1);
+    rb_define_method(cDSA, "sysverify", ossl_dsa_verify, 2);
+
+    DEF_OSSL_PKEY_BN(cDSA, dsa, p);
+    DEF_OSSL_PKEY_BN(cDSA, dsa, q);
+    DEF_OSSL_PKEY_BN(cDSA, dsa, g);
+    DEF_OSSL_PKEY_BN(cDSA, dsa, pub_key);
+    DEF_OSSL_PKEY_BN(cDSA, dsa, priv_key);
+    rb_define_method(cDSA, "set_pqg", ossl_dsa_set_pqg, 3);
+    rb_define_method(cDSA, "set_key", ossl_dsa_set_key, 2);
+
+    rb_define_method(cDSA, "params", ossl_dsa_get_params, 0);
+}
+
+#else /* defined NO_DSA */
+void
+Init_ossl_dsa(void)
+{
+}
+#endif /* NO_DSA */
diff --git a/ext/openssl/ossl_pkey_ec.c b/ext/openssl/ossl_pkey_ec.c
new file mode 100644
index 0000000..10800d2
--- /dev/null
+++ b/ext/openssl/ossl_pkey_ec.c
@@ -0,0 +1,1824 @@
+/*
+ * Copyright (C) 2006-2007 Technorama Ltd. <oss-ruby at technorama.net>
+ */
+
+#include "ossl.h"
+
+#if !defined(OPENSSL_NO_EC) && (OPENSSL_VERSION_NUMBER >= 0x0090802fL)
+
+#define EXPORT_PEM 0
+#define EXPORT_DER 1
+
+static const rb_data_type_t ossl_ec_group_type;
+static const rb_data_type_t ossl_ec_point_type;
+
+#define GetPKeyEC(obj, pkey) do { \
+    GetPKey((obj), (pkey)); \
+    if (EVP_PKEY_base_id(pkey) != EVP_PKEY_EC) { \
+	ossl_raise(rb_eRuntimeError, "THIS IS NOT A EC PKEY!"); \
+    } \
+} while (0)
+#define GetEC(obj, key) do { \
+    EVP_PKEY *_pkey; \
+    GetPKeyEC(obj, _pkey); \
+    (key) = EVP_PKEY_get0_EC_KEY(_pkey); \
+} while (0)
+#define SafeGetEC(obj, key) do { \
+    OSSL_Check_Kind(obj, cEC); \
+    GetEC(obj, key); \
+} while (0)
+
+#define GetECGroup(obj, group) do { \
+    TypedData_Get_Struct(obj, EC_GROUP, &ossl_ec_group_type, group); \
+    if ((group) == NULL) \
+	ossl_raise(eEC_GROUP, "EC_GROUP is not initialized"); \
+} while (0)
+#define SafeGetECGroup(obj, group) do { \
+    OSSL_Check_Kind((obj), cEC_GROUP); \
+    GetECGroup(obj, group); \
+} while (0)
+
+#define GetECPoint(obj, point) do { \
+    TypedData_Get_Struct(obj, EC_POINT, &ossl_ec_point_type, point); \
+    if ((point) == NULL) \
+	ossl_raise(eEC_POINT, "EC_POINT is not initialized"); \
+} while (0)
+#define SafeGetECPoint(obj, point) do { \
+    OSSL_Check_Kind((obj), cEC_POINT); \
+    GetECPoint(obj, point); \
+} while(0)
+#define GetECPointGroup(obj, group) do { \
+    VALUE _group = rb_attr_get(obj, id_i_group); \
+    SafeGetECGroup(_group, group); \
+} while (0)
+
+VALUE cEC;
+VALUE eECError;
+VALUE cEC_GROUP;
+VALUE eEC_GROUP;
+VALUE cEC_POINT;
+VALUE eEC_POINT;
+
+static ID s_GFp;
+static ID s_GFp_simple;
+static ID s_GFp_mont;
+static ID s_GFp_nist;
+static ID s_GF2m;
+static ID s_GF2m_simple;
+
+static ID ID_uncompressed;
+static ID ID_compressed;
+static ID ID_hybrid;
+
+static ID id_i_group;
+
+static VALUE ec_group_new(const EC_GROUP *group);
+static VALUE ec_point_new(const EC_POINT *point, const EC_GROUP *group);
+
+static VALUE ec_instance(VALUE klass, EC_KEY *ec)
+{
+    EVP_PKEY *pkey;
+    VALUE obj;
+
+    if (!ec) {
+	return Qfalse;
+    }
+    obj = NewPKey(klass);
+    if (!(pkey = EVP_PKEY_new())) {
+	return Qfalse;
+    }
+    if (!EVP_PKEY_assign_EC_KEY(pkey, ec)) {
+	EVP_PKEY_free(pkey);
+	return Qfalse;
+    }
+    SetPKey(obj, pkey);
+
+    return obj;
+}
+
+VALUE ossl_ec_new(EVP_PKEY *pkey)
+{
+    VALUE obj;
+
+    if (!pkey) {
+	obj = ec_instance(cEC, EC_KEY_new());
+    } else {
+	obj = NewPKey(cEC);
+	if (EVP_PKEY_base_id(pkey) != EVP_PKEY_EC) {
+	    ossl_raise(rb_eTypeError, "Not a EC key!");
+	}
+	SetPKey(obj, pkey);
+    }
+    if (obj == Qfalse) {
+	ossl_raise(eECError, NULL);
+    }
+
+    return obj;
+}
+
+/*
+ * Creates a new EC_KEY on the EC group obj. arg can be an EC::Group or a String
+ * representing an OID.
+ */
+static EC_KEY *
+ec_key_new_from_group(VALUE arg)
+{
+    EC_KEY *ec;
+
+    if (rb_obj_is_kind_of(arg, cEC_GROUP)) {
+	EC_GROUP *group;
+
+	SafeGetECGroup(arg, group);
+	if (!(ec = EC_KEY_new()))
+	    ossl_raise(eECError, NULL);
+
+	if (!EC_KEY_set_group(ec, group)) {
+	    EC_KEY_free(ec);
+	    ossl_raise(eECError, NULL);
+	}
+    } else {
+	int nid = OBJ_sn2nid(StringValueCStr(arg));
+
+	if (nid == NID_undef)
+	    ossl_raise(eECError, "invalid curve name");
+
+	if (!(ec = EC_KEY_new_by_curve_name(nid)))
+	    ossl_raise(eECError, NULL);
+
+	EC_KEY_set_asn1_flag(ec, OPENSSL_EC_NAMED_CURVE);
+	EC_KEY_set_conv_form(ec, POINT_CONVERSION_UNCOMPRESSED);
+    }
+
+    return ec;
+}
+
+/*
+ *  call-seq:
+ *     EC.generate(ec_group) -> ec
+ *     EC.generate(string) -> ec
+ *
+ * Creates a new EC instance with a new random private and public key.
+ */
+static VALUE
+ossl_ec_key_s_generate(VALUE klass, VALUE arg)
+{
+    EC_KEY *ec;
+    VALUE obj;
+
+    ec = ec_key_new_from_group(arg);
+
+    obj = ec_instance(klass, ec);
+    if (obj == Qfalse) {
+	EC_KEY_free(ec);
+	ossl_raise(eECError, NULL);
+    }
+
+    if (!EC_KEY_generate_key(ec))
+	ossl_raise(eECError, "EC_KEY_generate_key");
+
+    return obj;
+}
+
+/*
+ * call-seq:
+ *   OpenSSL::PKey::EC.new
+ *   OpenSSL::PKey::EC.new(ec_key)
+ *   OpenSSL::PKey::EC.new(ec_group)
+ *   OpenSSL::PKey::EC.new("secp112r1")
+ *   OpenSSL::PKey::EC.new(pem_string [, pwd])
+ *   OpenSSL::PKey::EC.new(der_string)
+ *
+ * Creates a new EC object from given arguments.
+ */
+static VALUE ossl_ec_key_initialize(int argc, VALUE *argv, VALUE self)
+{
+    EVP_PKEY *pkey;
+    EC_KEY *ec;
+    VALUE arg, pass;
+
+    GetPKey(self, pkey);
+    if (EVP_PKEY_base_id(pkey) != EVP_PKEY_NONE)
+        ossl_raise(eECError, "EC_KEY already initialized");
+
+    rb_scan_args(argc, argv, "02", &arg, &pass);
+
+    if (NIL_P(arg)) {
+        if (!(ec = EC_KEY_new()))
+	    ossl_raise(eECError, NULL);
+    } else if (rb_obj_is_kind_of(arg, cEC)) {
+	EC_KEY *other_ec = NULL;
+
+	SafeGetEC(arg, other_ec);
+	if (!(ec = EC_KEY_dup(other_ec)))
+	    ossl_raise(eECError, NULL);
+    } else if (rb_obj_is_kind_of(arg, cEC_GROUP)) {
+	ec = ec_key_new_from_group(arg);
+    } else {
+	BIO *in;
+
+	pass = ossl_pem_passwd_value(pass);
+	in = ossl_obj2bio(arg);
+
+	ec = PEM_read_bio_ECPrivateKey(in, NULL, ossl_pem_passwd_cb, (void *)pass);
+	if (!ec) {
+	    OSSL_BIO_reset(in);
+	    ec = PEM_read_bio_EC_PUBKEY(in, NULL, ossl_pem_passwd_cb, (void *)pass);
+	}
+	if (!ec) {
+	    OSSL_BIO_reset(in);
+	    ec = d2i_ECPrivateKey_bio(in, NULL);
+	}
+	if (!ec) {
+	    OSSL_BIO_reset(in);
+	    ec = d2i_EC_PUBKEY_bio(in, NULL);
+	}
+	BIO_free(in);
+
+	if (!ec) {
+	    ossl_clear_error();
+	    ec = ec_key_new_from_group(arg);
+	}
+    }
+
+    if (!EVP_PKEY_assign_EC_KEY(pkey, ec)) {
+	EC_KEY_free(ec);
+	ossl_raise(eECError, "EVP_PKEY_assign_EC_KEY");
+    }
+
+    return self;
+}
+
+static VALUE
+ossl_ec_key_initialize_copy(VALUE self, VALUE other)
+{
+    EVP_PKEY *pkey;
+    EC_KEY *ec, *ec_new;
+
+    GetPKey(self, pkey);
+    if (EVP_PKEY_base_id(pkey) != EVP_PKEY_NONE)
+	ossl_raise(eECError, "EC already initialized");
+    SafeGetEC(other, ec);
+
+    ec_new = EC_KEY_dup(ec);
+    if (!ec_new)
+	ossl_raise(eECError, "EC_KEY_dup");
+    if (!EVP_PKEY_assign_EC_KEY(pkey, ec_new)) {
+	EC_KEY_free(ec_new);
+	ossl_raise(eECError, "EVP_PKEY_assign_EC_KEY");
+    }
+
+    return self;
+}
+
+/*
+ * call-seq:
+ *   key.group   => group
+ *
+ * Returns the EC::Group that the key is associated with. Modifying the returned
+ * group does not affect +key+.
+ */
+static VALUE
+ossl_ec_key_get_group(VALUE self)
+{
+    EC_KEY *ec;
+    const EC_GROUP *group;
+
+    GetEC(self, ec);
+    group = EC_KEY_get0_group(ec);
+    if (!group)
+	return Qnil;
+
+    return ec_group_new(group);
+}
+
+/*
+ * call-seq:
+ *   key.group = group
+ *
+ * Sets the EC::Group for the key. The group structure is internally copied so
+ * modification to +group+ after assigning to a key has no effect on the key.
+ */
+static VALUE
+ossl_ec_key_set_group(VALUE self, VALUE group_v)
+{
+    EC_KEY *ec;
+    EC_GROUP *group;
+
+    GetEC(self, ec);
+    SafeGetECGroup(group_v, group);
+
+    if (EC_KEY_set_group(ec, group) != 1)
+        ossl_raise(eECError, "EC_KEY_set_group");
+
+    return group_v;
+}
+
+/*
+ *  call-seq:
+ *     key.private_key   => OpenSSL::BN
+ *
+ *  See the OpenSSL documentation for EC_KEY_get0_private_key()
+ */
+static VALUE ossl_ec_key_get_private_key(VALUE self)
+{
+    EC_KEY *ec;
+    const BIGNUM *bn;
+
+    GetEC(self, ec);
+    if ((bn = EC_KEY_get0_private_key(ec)) == NULL)
+        return Qnil;
+
+    return ossl_bn_new(bn);
+}
+
+/*
+ *  call-seq:
+ *     key.private_key = openssl_bn
+ *
+ *  See the OpenSSL documentation for EC_KEY_set_private_key()
+ */
+static VALUE ossl_ec_key_set_private_key(VALUE self, VALUE private_key)
+{
+    EC_KEY *ec;
+    BIGNUM *bn = NULL;
+
+    GetEC(self, ec);
+    if (!NIL_P(private_key))
+        bn = GetBNPtr(private_key);
+
+    switch (EC_KEY_set_private_key(ec, bn)) {
+    case 1:
+        break;
+    case 0:
+        if (bn == NULL)
+            break;
+    default:
+        ossl_raise(eECError, "EC_KEY_set_private_key");
+    }
+
+    return private_key;
+}
+
+/*
+ *  call-seq:
+ *     key.public_key   => OpenSSL::PKey::EC::Point
+ *
+ *  See the OpenSSL documentation for EC_KEY_get0_public_key()
+ */
+static VALUE ossl_ec_key_get_public_key(VALUE self)
+{
+    EC_KEY *ec;
+    const EC_POINT *point;
+
+    GetEC(self, ec);
+    if ((point = EC_KEY_get0_public_key(ec)) == NULL)
+        return Qnil;
+
+    return ec_point_new(point, EC_KEY_get0_group(ec));
+}
+
+/*
+ *  call-seq:
+ *     key.public_key = ec_point
+ *
+ *  See the OpenSSL documentation for EC_KEY_set_public_key()
+ */
+static VALUE ossl_ec_key_set_public_key(VALUE self, VALUE public_key)
+{
+    EC_KEY *ec;
+    EC_POINT *point = NULL;
+
+    GetEC(self, ec);
+    if (!NIL_P(public_key))
+        SafeGetECPoint(public_key, point);
+
+    switch (EC_KEY_set_public_key(ec, point)) {
+    case 1:
+        break;
+    case 0:
+        if (point == NULL)
+            break;
+    default:
+        ossl_raise(eECError, "EC_KEY_set_public_key");
+    }
+
+    return public_key;
+}
+
+/*
+ *  call-seq:
+ *     key.public? => true or false
+ *
+ *  Returns whether this EC instance has a public key. The public key
+ *  (EC::Point) can be retrieved with EC#public_key.
+ */
+static VALUE ossl_ec_key_is_public(VALUE self)
+{
+    EC_KEY *ec;
+
+    GetEC(self, ec);
+
+    return EC_KEY_get0_public_key(ec) ? Qtrue : Qfalse;
+}
+
+/*
+ *  call-seq:
+ *     key.private? => true or false
+ *
+ *  Returns whether this EC instance has a private key. The private key (BN) can
+ *  be retrieved with EC#private_key.
+ */
+static VALUE ossl_ec_key_is_private(VALUE self)
+{
+    EC_KEY *ec;
+
+    GetEC(self, ec);
+
+    return EC_KEY_get0_private_key(ec) ? Qtrue : Qfalse;
+}
+
+static VALUE ossl_ec_key_to_string(VALUE self, VALUE ciph, VALUE pass, int format)
+{
+    EC_KEY *ec;
+    BIO *out;
+    int i = -1;
+    int private = 0;
+    VALUE str;
+    const EVP_CIPHER *cipher = NULL;
+
+    GetEC(self, ec);
+
+    if (EC_KEY_get0_public_key(ec) == NULL)
+        ossl_raise(eECError, "can't export - no public key set");
+
+    if (EC_KEY_check_key(ec) != 1)
+	ossl_raise(eECError, "can't export - EC_KEY_check_key failed");
+
+    if (EC_KEY_get0_private_key(ec))
+        private = 1;
+
+    if (!NIL_P(ciph)) {
+	cipher = GetCipherPtr(ciph);
+	pass = ossl_pem_passwd_value(pass);
+    }
+
+    if (!(out = BIO_new(BIO_s_mem())))
+        ossl_raise(eECError, "BIO_new(BIO_s_mem())");
+
+    switch(format) {
+    case EXPORT_PEM:
+    	if (private) {
+            i = PEM_write_bio_ECPrivateKey(out, ec, cipher, NULL, 0, ossl_pem_passwd_cb, (void *)pass);
+    	} else {
+            i = PEM_write_bio_EC_PUBKEY(out, ec);
+        }
+
+    	break;
+    case EXPORT_DER:
+        if (private) {
+            i = i2d_ECPrivateKey_bio(out, ec);
+        } else {
+            i = i2d_EC_PUBKEY_bio(out, ec);
+        }
+
+    	break;
+    default:
+        BIO_free(out);
+    	ossl_raise(rb_eRuntimeError, "unknown format (internal error)");
+    }
+
+    if (i != 1) {
+        BIO_free(out);
+        ossl_raise(eECError, "outlen=%d", i);
+    }
+
+    str = ossl_membio2str(out);
+
+    return str;
+}
+
+/*
+ *  call-seq:
+ *     key.export([cipher, pass_phrase]) => String
+ *     key.to_pem([cipher, pass_phrase]) => String
+ *
+ * Outputs the EC key in PEM encoding.  If +cipher+ and +pass_phrase+ are given
+ * they will be used to encrypt the key.  +cipher+ must be an OpenSSL::Cipher
+ * instance. Note that encryption will only be effective for a private key,
+ * public keys will always be encoded in plain text.
+ */
+static VALUE ossl_ec_key_export(int argc, VALUE *argv, VALUE self)
+{
+    VALUE cipher, passwd;
+    rb_scan_args(argc, argv, "02", &cipher, &passwd);
+    return ossl_ec_key_to_string(self, cipher, passwd, EXPORT_PEM);
+}
+
+/*
+ *  call-seq:
+ *     key.to_der   => String
+ *
+ *  See the OpenSSL documentation for i2d_ECPrivateKey_bio()
+ */
+static VALUE ossl_ec_key_to_der(VALUE self)
+{
+    return ossl_ec_key_to_string(self, Qnil, Qnil, EXPORT_DER);
+}
+
+/*
+ *  call-seq:
+ *     key.to_text   => String
+ *
+ *  See the OpenSSL documentation for EC_KEY_print()
+ */
+static VALUE ossl_ec_key_to_text(VALUE self)
+{
+    EC_KEY *ec;
+    BIO *out;
+    VALUE str;
+
+    GetEC(self, ec);
+    if (!(out = BIO_new(BIO_s_mem()))) {
+	ossl_raise(eECError, "BIO_new(BIO_s_mem())");
+    }
+    if (!EC_KEY_print(out, ec, 0)) {
+	BIO_free(out);
+	ossl_raise(eECError, "EC_KEY_print");
+    }
+    str = ossl_membio2str(out);
+
+    return str;
+}
+
+/*
+ *  call-seq:
+ *     key.generate_key!   => self
+ *
+ * Generates a new random private and public key.
+ *
+ * See also the OpenSSL documentation for EC_KEY_generate_key()
+ *
+ * === Example
+ *   ec = OpenSSL::PKey::EC.new("prime256v1")
+ *   p ec.private_key # => nil
+ *   ec.generate_key!
+ *   p ec.private_key # => #<OpenSSL::BN XXXXXX>
+ */
+static VALUE ossl_ec_key_generate_key(VALUE self)
+{
+    EC_KEY *ec;
+
+    GetEC(self, ec);
+    if (EC_KEY_generate_key(ec) != 1)
+	ossl_raise(eECError, "EC_KEY_generate_key");
+
+    return self;
+}
+
+/*
+ *  call-seq:
+ *     key.check_key   => true
+ *
+ *  Raises an exception if the key is invalid.
+ *
+ *  See the OpenSSL documentation for EC_KEY_check_key()
+ */
+static VALUE ossl_ec_key_check_key(VALUE self)
+{
+    EC_KEY *ec;
+
+    GetEC(self, ec);
+    if (EC_KEY_check_key(ec) != 1)
+	ossl_raise(eECError, "EC_KEY_check_key");
+
+    return Qtrue;
+}
+
+/*
+ *  call-seq:
+ *     key.dh_compute_key(pubkey)   => String
+ *
+ *  See the OpenSSL documentation for ECDH_compute_key()
+ */
+static VALUE ossl_ec_key_dh_compute_key(VALUE self, VALUE pubkey)
+{
+    EC_KEY *ec;
+    EC_POINT *point;
+    int buf_len;
+    VALUE str;
+
+    GetEC(self, ec);
+    SafeGetECPoint(pubkey, point);
+
+/* BUG: need a way to figure out the maximum string size */
+    buf_len = 1024;
+    str = rb_str_new(0, buf_len);
+/* BUG: take KDF as a block */
+    buf_len = ECDH_compute_key(RSTRING_PTR(str), buf_len, point, ec, NULL);
+    if (buf_len < 0)
+         ossl_raise(eECError, "ECDH_compute_key");
+
+    rb_str_resize(str, buf_len);
+
+    return str;
+}
+
+/* sign_setup */
+
+/*
+ *  call-seq:
+ *     key.dsa_sign_asn1(data)   => String
+ *
+ *  See the OpenSSL documentation for ECDSA_sign()
+ */
+static VALUE ossl_ec_key_dsa_sign_asn1(VALUE self, VALUE data)
+{
+    EC_KEY *ec;
+    unsigned int buf_len;
+    VALUE str;
+
+    GetEC(self, ec);
+    StringValue(data);
+
+    if (EC_KEY_get0_private_key(ec) == NULL)
+	ossl_raise(eECError, "Private EC key needed!");
+
+    str = rb_str_new(0, ECDSA_size(ec));
+    if (ECDSA_sign(0, (unsigned char *) RSTRING_PTR(data), RSTRING_LENINT(data), (unsigned char *) RSTRING_PTR(str), &buf_len, ec) != 1)
+	ossl_raise(eECError, "ECDSA_sign");
+    rb_str_set_len(str, buf_len);
+
+    return str;
+}
+
+/*
+ *  call-seq:
+ *     key.dsa_verify_asn1(data, sig)   => true or false
+ *
+ *  See the OpenSSL documentation for ECDSA_verify()
+ */
+static VALUE ossl_ec_key_dsa_verify_asn1(VALUE self, VALUE data, VALUE sig)
+{
+    EC_KEY *ec;
+
+    GetEC(self, ec);
+    StringValue(data);
+    StringValue(sig);
+
+    switch (ECDSA_verify(0, (unsigned char *) RSTRING_PTR(data), RSTRING_LENINT(data), (unsigned char *) RSTRING_PTR(sig), (int)RSTRING_LEN(sig), ec)) {
+    case 1:	return Qtrue;
+    case 0:	return Qfalse;
+    default:	break;
+    }
+
+    ossl_raise(eECError, "ECDSA_verify");
+
+    UNREACHABLE;
+}
+
+/*
+ * OpenSSL::PKey::EC::Group
+ */
+static void
+ossl_ec_group_free(void *ptr)
+{
+    EC_GROUP_clear_free(ptr);
+}
+
+static const rb_data_type_t ossl_ec_group_type = {
+    "OpenSSL/ec_group",
+    {
+	0, ossl_ec_group_free,
+    },
+    0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
+};
+
+static VALUE
+ossl_ec_group_alloc(VALUE klass)
+{
+    return TypedData_Wrap_Struct(klass, &ossl_ec_group_type, NULL);
+}
+
+static VALUE
+ec_group_new(const EC_GROUP *group)
+{
+    VALUE obj;
+    EC_GROUP *group_new;
+
+    obj = ossl_ec_group_alloc(cEC_GROUP);
+    group_new = EC_GROUP_dup(group);
+    if (!group_new)
+	ossl_raise(eEC_GROUP, "EC_GROUP_dup");
+    RTYPEDDATA_DATA(obj) = group_new;
+
+    return obj;
+}
+
+/*
+ * call-seq:
+ *   OpenSSL::PKey::EC::Group.new(ec_group)
+ *   OpenSSL::PKey::EC::Group.new(pem_or_der_encoded)
+ *   OpenSSL::PKey::EC::Group.new(ec_method)
+ *   OpenSSL::PKey::EC::Group.new(:GFp, bignum_p, bignum_a, bignum_b)
+ *   OpenSSL::PKey::EC::Group.new(:GF2m, bignum_p, bignum_a, bignum_b)
+ *
+ * Creates a new EC::Group object.
+ *
+ * +ec_method+ is a symbol that represents an EC_METHOD. Currently the following
+ * are supported:
+ *
+ * * :GFp_simple
+ * * :GFp_mont
+ * * :GFp_nist
+ * * :GF2m_simple
+ *
+ * If the first argument is :GFp or :GF2m, creates a new curve with given
+ * parameters.
+ */
+static VALUE ossl_ec_group_initialize(int argc, VALUE *argv, VALUE self)
+{
+    VALUE arg1, arg2, arg3, arg4;
+    EC_GROUP *group;
+
+    TypedData_Get_Struct(self, EC_GROUP, &ossl_ec_group_type, group);
+    if (group)
+        ossl_raise(rb_eRuntimeError, "EC_GROUP is already initialized");
+
+    switch (rb_scan_args(argc, argv, "13", &arg1, &arg2, &arg3, &arg4)) {
+    case 1:
+        if (SYMBOL_P(arg1)) {
+            const EC_METHOD *method = NULL;
+            ID id = SYM2ID(arg1);
+
+            if (id == s_GFp_simple) {
+                method = EC_GFp_simple_method();
+            } else if (id == s_GFp_mont) {
+                method = EC_GFp_mont_method();
+            } else if (id == s_GFp_nist) {
+                method = EC_GFp_nist_method();
+#if !defined(OPENSSL_NO_EC2M)
+            } else if (id == s_GF2m_simple) {
+                method = EC_GF2m_simple_method();
+#endif
+            }
+
+            if (method) {
+                if ((group = EC_GROUP_new(method)) == NULL)
+                    ossl_raise(eEC_GROUP, "EC_GROUP_new");
+            } else {
+                ossl_raise(rb_eArgError, "unknown symbol, must be :GFp_simple, :GFp_mont, :GFp_nist or :GF2m_simple");
+            }
+        } else if (rb_obj_is_kind_of(arg1, cEC_GROUP)) {
+            const EC_GROUP *arg1_group;
+
+            SafeGetECGroup(arg1, arg1_group);
+            if ((group = EC_GROUP_dup(arg1_group)) == NULL)
+                ossl_raise(eEC_GROUP, "EC_GROUP_dup");
+        } else {
+            BIO *in = ossl_obj2bio(arg1);
+
+            group = PEM_read_bio_ECPKParameters(in, NULL, NULL, NULL);
+            if (!group) {
+		OSSL_BIO_reset(in);
+                group = d2i_ECPKParameters_bio(in, NULL);
+            }
+
+            BIO_free(in);
+
+            if (!group) {
+                const char *name = StringValueCStr(arg1);
+                int nid = OBJ_sn2nid(name);
+
+		ossl_clear_error(); /* ignore errors in d2i_ECPKParameters_bio() */
+                if (nid == NID_undef)
+                    ossl_raise(eEC_GROUP, "unknown curve name (%"PRIsVALUE")", arg1);
+
+                group = EC_GROUP_new_by_curve_name(nid);
+                if (group == NULL)
+                    ossl_raise(eEC_GROUP, "unable to create curve (%"PRIsVALUE")", arg1);
+
+                EC_GROUP_set_asn1_flag(group, OPENSSL_EC_NAMED_CURVE);
+                EC_GROUP_set_point_conversion_form(group, POINT_CONVERSION_UNCOMPRESSED);
+            }
+        }
+
+        break;
+    case 4:
+        if (SYMBOL_P(arg1)) {
+            ID id = SYM2ID(arg1);
+            EC_GROUP *(*new_curve)(const BIGNUM *, const BIGNUM *, const BIGNUM *, BN_CTX *) = NULL;
+            const BIGNUM *p = GetBNPtr(arg2);
+            const BIGNUM *a = GetBNPtr(arg3);
+            const BIGNUM *b = GetBNPtr(arg4);
+
+            if (id == s_GFp) {
+                new_curve = EC_GROUP_new_curve_GFp;
+#if !defined(OPENSSL_NO_EC2M)
+            } else if (id == s_GF2m) {
+                new_curve = EC_GROUP_new_curve_GF2m;
+#endif
+            } else {
+                ossl_raise(rb_eArgError, "unknown symbol, must be :GFp or :GF2m");
+            }
+
+            if ((group = new_curve(p, a, b, ossl_bn_ctx)) == NULL)
+                ossl_raise(eEC_GROUP, "EC_GROUP_new_by_GF*");
+        } else {
+             ossl_raise(rb_eArgError, "unknown argument, must be :GFp or :GF2m");
+        }
+
+        break;
+    default:
+        ossl_raise(rb_eArgError, "wrong number of arguments");
+    }
+
+    if (group == NULL)
+        ossl_raise(eEC_GROUP, "");
+    RTYPEDDATA_DATA(self) = group;
+
+    return self;
+}
+
+static VALUE
+ossl_ec_group_initialize_copy(VALUE self, VALUE other)
+{
+    EC_GROUP *group, *group_new;
+
+    TypedData_Get_Struct(self, EC_GROUP, &ossl_ec_group_type, group_new);
+    if (group_new)
+	ossl_raise(eEC_GROUP, "EC::Group already initialized");
+    SafeGetECGroup(other, group);
+
+    group_new = EC_GROUP_dup(group);
+    if (!group_new)
+	ossl_raise(eEC_GROUP, "EC_GROUP_dup");
+    RTYPEDDATA_DATA(self) = group_new;
+
+    return self;
+}
+
+/*
+ * call-seq:
+ *   group1.eql?(group2)   => true | false
+ *   group1 == group2   => true | false
+ *
+ * Returns true if the two groups use the same curve and have the same
+ * parameters, false otherwise.
+ */
+static VALUE ossl_ec_group_eql(VALUE a, VALUE b)
+{
+    EC_GROUP *group1 = NULL, *group2 = NULL;
+
+    GetECGroup(a, group1);
+    SafeGetECGroup(b, group2);
+
+    if (EC_GROUP_cmp(group1, group2, ossl_bn_ctx) == 1)
+       return Qfalse;
+
+    return Qtrue;
+}
+
+/*
+ * call-seq:
+ *   group.generator   => ec_point
+ *
+ * Returns the generator of the group.
+ *
+ * See the OpenSSL documentation for EC_GROUP_get0_generator()
+ */
+static VALUE ossl_ec_group_get_generator(VALUE self)
+{
+    EC_GROUP *group;
+    const EC_POINT *generator;
+
+    GetECGroup(self, group);
+    generator = EC_GROUP_get0_generator(group);
+    if (!generator)
+	return Qnil;
+
+    return ec_point_new(generator, group);
+}
+
+/*
+ * call-seq:
+ *   group.set_generator(generator, order, cofactor)   => self
+ *
+ * Sets the curve parameters. +generator+ must be an instance of EC::Point that
+ * is on the curve. +order+ and +cofactor+ are integers.
+ *
+ * See the OpenSSL documentation for EC_GROUP_set_generator()
+ */
+static VALUE ossl_ec_group_set_generator(VALUE self, VALUE generator, VALUE order, VALUE cofactor)
+{
+    EC_GROUP *group = NULL;
+    const EC_POINT *point;
+    const BIGNUM *o, *co;
+
+    GetECGroup(self, group);
+    SafeGetECPoint(generator, point);
+    o = GetBNPtr(order);
+    co = GetBNPtr(cofactor);
+
+    if (EC_GROUP_set_generator(group, point, o, co) != 1)
+        ossl_raise(eEC_GROUP, "EC_GROUP_set_generator");
+
+    return self;
+}
+
+/*
+ * call-seq:
+ *   group.get_order   => order_bn
+ *
+ * Returns the order of the group.
+ *
+ * See the OpenSSL documentation for EC_GROUP_get_order()
+ */
+static VALUE ossl_ec_group_get_order(VALUE self)
+{
+    VALUE bn_obj;
+    BIGNUM *bn;
+    EC_GROUP *group = NULL;
+
+    GetECGroup(self, group);
+
+    bn_obj = ossl_bn_new(NULL);
+    bn = GetBNPtr(bn_obj);
+
+    if (EC_GROUP_get_order(group, bn, ossl_bn_ctx) != 1)
+        ossl_raise(eEC_GROUP, "EC_GROUP_get_order");
+
+    return bn_obj;
+}
+
+/*
+ * call-seq:
+ *   group.get_cofactor   => cofactor_bn
+ *
+ * Returns the cofactor of the group.
+ *
+ * See the OpenSSL documentation for EC_GROUP_get_cofactor()
+ */
+static VALUE ossl_ec_group_get_cofactor(VALUE self)
+{
+    VALUE bn_obj;
+    BIGNUM *bn;
+    EC_GROUP *group = NULL;
+
+    GetECGroup(self, group);
+
+    bn_obj = ossl_bn_new(NULL);
+    bn = GetBNPtr(bn_obj);
+
+    if (EC_GROUP_get_cofactor(group, bn, ossl_bn_ctx) != 1)
+        ossl_raise(eEC_GROUP, "EC_GROUP_get_cofactor");
+
+    return bn_obj;
+}
+
+/*
+ * call-seq:
+ *   group.curve_name  => String
+ *
+ * Returns the curve name (sn).
+ *
+ * See the OpenSSL documentation for EC_GROUP_get_curve_name()
+ */
+static VALUE ossl_ec_group_get_curve_name(VALUE self)
+{
+    EC_GROUP *group = NULL;
+    int nid;
+
+    GetECGroup(self, group);
+    if (group == NULL)
+        return Qnil;
+
+    nid = EC_GROUP_get_curve_name(group);
+
+/* BUG: an nid or asn1 object should be returned, maybe. */
+    return rb_str_new2(OBJ_nid2sn(nid));
+}
+
+/*
+ * call-seq:
+ *   EC.builtin_curves => [[sn, comment], ...]
+ *
+ * Obtains a list of all predefined curves by the OpenSSL. Curve names are
+ * returned as sn.
+ *
+ * See the OpenSSL documentation for EC_get_builtin_curves().
+ */
+static VALUE ossl_s_builtin_curves(VALUE self)
+{
+    EC_builtin_curve *curves = NULL;
+    int n;
+    int crv_len = rb_long2int(EC_get_builtin_curves(NULL, 0));
+    VALUE ary, ret;
+
+    curves = ALLOCA_N(EC_builtin_curve, crv_len);
+    if (curves == NULL)
+        return Qnil;
+    if (!EC_get_builtin_curves(curves, crv_len))
+        ossl_raise(rb_eRuntimeError, "EC_get_builtin_curves");
+
+    ret = rb_ary_new2(crv_len);
+
+    for (n = 0; n < crv_len; n++) {
+        const char *sname = OBJ_nid2sn(curves[n].nid);
+        const char *comment = curves[n].comment;
+
+        ary = rb_ary_new2(2);
+        rb_ary_push(ary, rb_str_new2(sname));
+        rb_ary_push(ary, comment ? rb_str_new2(comment) : Qnil);
+        rb_ary_push(ret, ary);
+    }
+
+    return ret;
+}
+
+/*
+ * call-seq:
+ *   group.asn1_flag -> Integer
+ *
+ * Returns the flags set on the group.
+ *
+ * See also #asn1_flag=.
+ */
+static VALUE ossl_ec_group_get_asn1_flag(VALUE self)
+{
+    EC_GROUP *group = NULL;
+    int flag;
+
+    GetECGroup(self, group);
+    flag = EC_GROUP_get_asn1_flag(group);
+
+    return INT2NUM(flag);
+}
+
+/*
+ * call-seq:
+ *   group.asn1_flag = flags
+ *
+ * Sets flags on the group. The flag value is used to determine how to encode
+ * the group: encode explicit parameters or named curve using an OID.
+ *
+ * The flag value can be either of:
+ *
+ * * EC::NAMED_CURVE
+ * * EC::EXPLICIT_CURVE
+ *
+ * See the OpenSSL documentation for EC_GROUP_set_asn1_flag().
+ */
+static VALUE ossl_ec_group_set_asn1_flag(VALUE self, VALUE flag_v)
+{
+    EC_GROUP *group = NULL;
+
+    GetECGroup(self, group);
+    EC_GROUP_set_asn1_flag(group, NUM2INT(flag_v));
+
+    return flag_v;
+}
+
+/*
+ * call-seq:
+ *   group.point_conversion_form -> Symbol
+ *
+ * Returns the form how EC::Point data is encoded as ASN.1.
+ *
+ * See also #point_conversion_form=.
+ */
+static VALUE ossl_ec_group_get_point_conversion_form(VALUE self)
+{
+    EC_GROUP *group = NULL;
+    point_conversion_form_t form;
+    VALUE ret;
+
+    GetECGroup(self, group);
+    form = EC_GROUP_get_point_conversion_form(group);
+
+    switch (form) {
+    case POINT_CONVERSION_UNCOMPRESSED:	ret = ID_uncompressed; break;
+    case POINT_CONVERSION_COMPRESSED:	ret = ID_compressed; break;
+    case POINT_CONVERSION_HYBRID:	ret = ID_hybrid; break;
+    default:	ossl_raise(eEC_GROUP, "unsupported point conversion form: %d, this module should be updated", form);
+    }
+
+   return ID2SYM(ret);
+}
+
+static point_conversion_form_t
+parse_point_conversion_form_symbol(VALUE sym)
+{
+    ID id = SYM2ID(sym);
+
+    if (id == ID_uncompressed)
+	return POINT_CONVERSION_UNCOMPRESSED;
+    else if (id == ID_compressed)
+	return POINT_CONVERSION_COMPRESSED;
+    else if (id == ID_hybrid)
+	return POINT_CONVERSION_HYBRID;
+    else
+	ossl_raise(rb_eArgError, "unsupported point conversion form %+"PRIsVALUE
+		   " (expected :compressed, :uncompressed, or :hybrid)", sym);
+}
+
+/*
+ * call-seq:
+ *   group.point_conversion_form = form
+ *
+ * Sets the form how EC::Point data is encoded as ASN.1 as defined in X9.62.
+ *
+ * +format+ can be one of these:
+ *
+ * :compressed::
+ *   Encoded as z||x, where z is an octet indicating which solution of the
+ *   equation y is. z will be 0x02 or 0x03.
+ * :uncompressed::
+ *   Encoded as z||x||y, where z is an octet 0x04.
+ * :hybrid::
+ *   Encodes as z||x||y, where z is an octet indicating which solution of the
+ *   equation y is. z will be 0x06 or 0x07.
+ *
+ * See the OpenSSL documentation for EC_GROUP_set_point_conversion_form()
+ */
+static VALUE
+ossl_ec_group_set_point_conversion_form(VALUE self, VALUE form_v)
+{
+    EC_GROUP *group;
+    point_conversion_form_t form;
+
+    GetECGroup(self, group);
+    form = parse_point_conversion_form_symbol(form_v);
+
+    EC_GROUP_set_point_conversion_form(group, form);
+
+    return form_v;
+}
+
+/*
+ * call-seq:
+ *   group.seed   => String or nil
+ *
+ * See the OpenSSL documentation for EC_GROUP_get0_seed()
+ */
+static VALUE ossl_ec_group_get_seed(VALUE self)
+{
+    EC_GROUP *group = NULL;
+    size_t seed_len;
+
+    GetECGroup(self, group);
+    seed_len = EC_GROUP_get_seed_len(group);
+
+    if (seed_len == 0)
+        return Qnil;
+
+    return rb_str_new((const char *)EC_GROUP_get0_seed(group), seed_len);
+}
+
+/*
+ * call-seq:
+ *   group.seed = seed  => seed
+ *
+ * See the OpenSSL documentation for EC_GROUP_set_seed()
+ */
+static VALUE ossl_ec_group_set_seed(VALUE self, VALUE seed)
+{
+    EC_GROUP *group = NULL;
+
+    GetECGroup(self, group);
+    StringValue(seed);
+
+    if (EC_GROUP_set_seed(group, (unsigned char *)RSTRING_PTR(seed), RSTRING_LEN(seed)) != (size_t)RSTRING_LEN(seed))
+        ossl_raise(eEC_GROUP, "EC_GROUP_set_seed");
+
+    return seed;
+}
+
+/* get/set curve GFp, GF2m */
+
+/*
+ * call-seq:
+ *   group.degree   => integer
+ *
+ * See the OpenSSL documentation for EC_GROUP_get_degree()
+ */
+static VALUE ossl_ec_group_get_degree(VALUE self)
+{
+    EC_GROUP *group = NULL;
+
+    GetECGroup(self, group);
+
+    return INT2NUM(EC_GROUP_get_degree(group));
+}
+
+static VALUE ossl_ec_group_to_string(VALUE self, int format)
+{
+    EC_GROUP *group;
+    BIO *out;
+    int i = -1;
+    VALUE str;
+
+    GetECGroup(self, group);
+
+    if (!(out = BIO_new(BIO_s_mem())))
+        ossl_raise(eEC_GROUP, "BIO_new(BIO_s_mem())");
+
+    switch(format) {
+    case EXPORT_PEM:
+        i = PEM_write_bio_ECPKParameters(out, group);
+    	break;
+    case EXPORT_DER:
+        i = i2d_ECPKParameters_bio(out, group);
+    	break;
+    default:
+        BIO_free(out);
+    	ossl_raise(rb_eRuntimeError, "unknown format (internal error)");
+    }
+
+    if (i != 1) {
+        BIO_free(out);
+        ossl_raise(eECError, NULL);
+    }
+
+    str = ossl_membio2str(out);
+
+    return str;
+}
+
+/*
+ * call-seq:
+ *   group.to_pem   => String
+ *
+ *  See the OpenSSL documentation for PEM_write_bio_ECPKParameters()
+ */
+static VALUE ossl_ec_group_to_pem(VALUE self)
+{
+    return ossl_ec_group_to_string(self, EXPORT_PEM);
+}
+
+/*
+ * call-seq:
+ *   group.to_der   => String
+ *
+ * See the OpenSSL documentation for i2d_ECPKParameters_bio()
+ */
+static VALUE ossl_ec_group_to_der(VALUE self)
+{
+    return ossl_ec_group_to_string(self, EXPORT_DER);
+}
+
+/*
+ * call-seq:
+ *   group.to_text   => String
+ *
+ * See the OpenSSL documentation for ECPKParameters_print()
+ */
+static VALUE ossl_ec_group_to_text(VALUE self)
+{
+    EC_GROUP *group;
+    BIO *out;
+    VALUE str;
+
+    GetECGroup(self, group);
+    if (!(out = BIO_new(BIO_s_mem()))) {
+	ossl_raise(eEC_GROUP, "BIO_new(BIO_s_mem())");
+    }
+    if (!ECPKParameters_print(out, group, 0)) {
+	BIO_free(out);
+	ossl_raise(eEC_GROUP, NULL);
+    }
+    str = ossl_membio2str(out);
+
+    return str;
+}
+
+
+/*
+ * OpenSSL::PKey::EC::Point
+ */
+static void
+ossl_ec_point_free(void *ptr)
+{
+    EC_POINT_clear_free(ptr);
+}
+
+static const rb_data_type_t ossl_ec_point_type = {
+    "OpenSSL/EC_POINT",
+    {
+	0, ossl_ec_point_free,
+    },
+    0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
+};
+
+static VALUE
+ossl_ec_point_alloc(VALUE klass)
+{
+    return TypedData_Wrap_Struct(klass, &ossl_ec_point_type, NULL);
+}
+
+static VALUE
+ec_point_new(const EC_POINT *point, const EC_GROUP *group)
+{
+    EC_POINT *point_new;
+    VALUE obj;
+
+    obj = ossl_ec_point_alloc(cEC_POINT);
+    point_new = EC_POINT_dup(point, group);
+    if (!point_new)
+	ossl_raise(eEC_POINT, "EC_POINT_dup");
+    RTYPEDDATA_DATA(obj) = point_new;
+    rb_ivar_set(obj, id_i_group, ec_group_new(group));
+
+    return obj;
+}
+
+/*
+ * call-seq:
+ *   OpenSSL::PKey::EC::Point.new(point)
+ *   OpenSSL::PKey::EC::Point.new(group)
+ *   OpenSSL::PKey::EC::Point.new(group, bn)
+ *
+ * See the OpenSSL documentation for EC_POINT_*
+ */
+static VALUE ossl_ec_point_initialize(int argc, VALUE *argv, VALUE self)
+{
+    EC_POINT *point;
+    VALUE arg1, arg2;
+    VALUE group_v = Qnil;
+    const EC_GROUP *group = NULL;
+
+    TypedData_Get_Struct(self, EC_POINT, &ossl_ec_point_type, point);
+    if (point)
+        ossl_raise(eEC_POINT, "EC_POINT already initialized");
+
+    switch (rb_scan_args(argc, argv, "11", &arg1, &arg2)) {
+    case 1:
+        if (rb_obj_is_kind_of(arg1, cEC_POINT)) {
+            const EC_POINT *arg_point;
+
+	    group_v = rb_attr_get(arg1, id_i_group);
+	    SafeGetECGroup(group_v, group);
+	    SafeGetECPoint(arg1, arg_point);
+
+            point = EC_POINT_dup(arg_point, group);
+        } else if (rb_obj_is_kind_of(arg1, cEC_GROUP)) {
+            group_v = arg1;
+            SafeGetECGroup(group_v, group);
+
+            point = EC_POINT_new(group);
+        } else {
+            ossl_raise(eEC_POINT, "wrong argument type: must be OpenSSL::PKey::EC::Point or OpenSSL::Pkey::EC::Group");
+        }
+
+        break;
+     case 2:
+        if (!rb_obj_is_kind_of(arg1, cEC_GROUP))
+            ossl_raise(rb_eArgError, "1st argument must be OpenSSL::PKey::EC::Group");
+        group_v = arg1;
+        SafeGetECGroup(group_v, group);
+
+        if (rb_obj_is_kind_of(arg2, cBN)) {
+            const BIGNUM *bn = GetBNPtr(arg2);
+
+            point = EC_POINT_bn2point(group, bn, NULL, ossl_bn_ctx);
+        } else {
+            BIO *in = ossl_obj2bio(arg1);
+
+/* BUG: finish me */
+
+            BIO_free(in);
+
+            if (point == NULL) {
+                ossl_raise(eEC_POINT, "unknown type for 2nd arg");
+            }
+        }
+        break;
+    default:
+        ossl_raise(rb_eArgError, "wrong number of arguments");
+    }
+
+    if (point == NULL)
+        ossl_raise(eEC_POINT, NULL);
+
+    if (NIL_P(group_v))
+        ossl_raise(rb_eRuntimeError, "missing group (internal error)");
+
+    RTYPEDDATA_DATA(self) = point;
+    rb_ivar_set(self, id_i_group, group_v);
+
+    return self;
+}
+
+static VALUE
+ossl_ec_point_initialize_copy(VALUE self, VALUE other)
+{
+    EC_POINT *point, *point_new;
+    EC_GROUP *group;
+    VALUE group_v;
+
+    TypedData_Get_Struct(self, EC_POINT, &ossl_ec_point_type, point_new);
+    if (point_new)
+	ossl_raise(eEC_POINT, "EC::Point already initialized");
+    SafeGetECPoint(other, point);
+
+    group_v = rb_obj_dup(rb_attr_get(other, id_i_group));
+    SafeGetECGroup(group_v, group);
+
+    point_new = EC_POINT_dup(point, group);
+    if (!point_new)
+	ossl_raise(eEC_POINT, "EC_POINT_dup");
+    RTYPEDDATA_DATA(self) = point_new;
+    rb_ivar_set(self, id_i_group, group_v);
+
+    return self;
+}
+
+/*
+ * call-seq:
+ *   point1.eql?(point2) => true | false
+ *   point1 == point2 => true | false
+ */
+static VALUE ossl_ec_point_eql(VALUE a, VALUE b)
+{
+    EC_POINT *point1, *point2;
+    VALUE group_v1 = rb_attr_get(a, id_i_group);
+    VALUE group_v2 = rb_attr_get(b, id_i_group);
+    const EC_GROUP *group;
+
+    if (ossl_ec_group_eql(group_v1, group_v2) == Qfalse)
+        return Qfalse;
+
+    GetECPoint(a, point1);
+    SafeGetECPoint(b, point2);
+    SafeGetECGroup(group_v1, group);
+
+    if (EC_POINT_cmp(group, point1, point2, ossl_bn_ctx) == 1)
+        return Qfalse;
+
+    return Qtrue;
+}
+
+/*
+ * call-seq:
+ *   point.infinity? => true | false
+ */
+static VALUE ossl_ec_point_is_at_infinity(VALUE self)
+{
+    EC_POINT *point;
+    const EC_GROUP *group;
+
+    GetECPoint(self, point);
+    GetECPointGroup(self, group);
+
+    switch (EC_POINT_is_at_infinity(group, point)) {
+    case 1: return Qtrue;
+    case 0: return Qfalse;
+    default: ossl_raise(cEC_POINT, "EC_POINT_is_at_infinity");
+    }
+
+    UNREACHABLE;
+}
+
+/*
+ * call-seq:
+ *   point.on_curve? => true | false
+ */
+static VALUE ossl_ec_point_is_on_curve(VALUE self)
+{
+    EC_POINT *point;
+    const EC_GROUP *group;
+
+    GetECPoint(self, point);
+    GetECPointGroup(self, group);
+
+    switch (EC_POINT_is_on_curve(group, point, ossl_bn_ctx)) {
+    case 1: return Qtrue;
+    case 0: return Qfalse;
+    default: ossl_raise(cEC_POINT, "EC_POINT_is_on_curve");
+    }
+
+    UNREACHABLE;
+}
+
+/*
+ * call-seq:
+ *   point.make_affine! => self
+ */
+static VALUE ossl_ec_point_make_affine(VALUE self)
+{
+    EC_POINT *point;
+    const EC_GROUP *group;
+
+    GetECPoint(self, point);
+    GetECPointGroup(self, group);
+
+    if (EC_POINT_make_affine(group, point, ossl_bn_ctx) != 1)
+        ossl_raise(cEC_POINT, "EC_POINT_make_affine");
+
+    return self;
+}
+
+/*
+ * call-seq:
+ *   point.invert! => self
+ */
+static VALUE ossl_ec_point_invert(VALUE self)
+{
+    EC_POINT *point;
+    const EC_GROUP *group;
+
+    GetECPoint(self, point);
+    GetECPointGroup(self, group);
+
+    if (EC_POINT_invert(group, point, ossl_bn_ctx) != 1)
+        ossl_raise(cEC_POINT, "EC_POINT_invert");
+
+    return self;
+}
+
+/*
+ * call-seq:
+ *   point.set_to_infinity! => self
+ */
+static VALUE ossl_ec_point_set_to_infinity(VALUE self)
+{
+    EC_POINT *point;
+    const EC_GROUP *group;
+
+    GetECPoint(self, point);
+    GetECPointGroup(self, group);
+
+    if (EC_POINT_set_to_infinity(group, point) != 1)
+        ossl_raise(cEC_POINT, "EC_POINT_set_to_infinity");
+
+    return self;
+}
+
+/*
+ * call-seq:
+ *   point.to_bn(conversion_form = nil) => OpenSSL::BN
+ *
+ * Convert the EC point into an octet string and store in an OpenSSL::BN. If
+ * +conversion_form+ is given, the point data is converted using the specified
+ * form. If not given, the default form set in the EC::Group object is used.
+ *
+ * See also EC::Point#point_conversion_form=.
+ */
+static VALUE
+ossl_ec_point_to_bn(int argc, VALUE *argv, VALUE self)
+{
+    EC_POINT *point;
+    VALUE form_obj, bn_obj;
+    const EC_GROUP *group;
+    point_conversion_form_t form;
+    BIGNUM *bn;
+
+    GetECPoint(self, point);
+    GetECPointGroup(self, group);
+    rb_scan_args(argc, argv, "01", &form_obj);
+    if (NIL_P(form_obj))
+	form = EC_GROUP_get_point_conversion_form(group);
+    else
+	form = parse_point_conversion_form_symbol(form_obj);
+
+    bn_obj = rb_obj_alloc(cBN);
+    bn = GetBNPtr(bn_obj);
+
+    if (EC_POINT_point2bn(group, point, form, bn, ossl_bn_ctx) == NULL)
+        ossl_raise(eEC_POINT, "EC_POINT_point2bn");
+
+    return bn_obj;
+}
+
+/*
+ * call-seq:
+ *   point.mul(bn1 [, bn2]) => point
+ *   point.mul(bns, points [, bn2]) => point
+ *
+ * Performs elliptic curve point multiplication.
+ *
+ * The first form calculates <tt>bn1 * point + bn2 * G</tt>, where +G+ is the
+ * generator of the group of +point+. +bn2+ may be omitted, and in that case,
+ * the result is just <tt>bn1 * point</tt>.
+ *
+ * The second form calculates <tt>bns[0] * point + bns[1] * points[0] + ...
+ * + bns[-1] * points[-1] + bn2 * G</tt>. +bn2+ may be omitted. +bns+ must be
+ * an array of OpenSSL::BN. +points+ must be an array of
+ * OpenSSL::PKey::EC::Point. Please note that <tt>points[0]</tt> is not
+ * multiplied by <tt>bns[0]</tt>, but <tt>bns[1]</tt>.
+ */
+static VALUE ossl_ec_point_mul(int argc, VALUE *argv, VALUE self)
+{
+    EC_POINT *point_self, *point_result;
+    const EC_GROUP *group;
+    VALUE group_v = rb_attr_get(self, id_i_group);
+    VALUE arg1, arg2, arg3, result;
+    const BIGNUM *bn_g = NULL;
+
+    GetECPoint(self, point_self);
+    SafeGetECGroup(group_v, group);
+
+    result = rb_obj_alloc(cEC_POINT);
+    ossl_ec_point_initialize(1, &group_v, result);
+    GetECPoint(result, point_result);
+
+    rb_scan_args(argc, argv, "12", &arg1, &arg2, &arg3);
+    if (!RB_TYPE_P(arg1, T_ARRAY)) {
+	BIGNUM *bn = GetBNPtr(arg1);
+
+	if (!NIL_P(arg2))
+	    bn_g = GetBNPtr(arg2);
+	if (EC_POINT_mul(group, point_result, bn_g, point_self, bn, ossl_bn_ctx) != 1)
+	    ossl_raise(eEC_POINT, NULL);
+    } else {
+	/*
+	 * bignums | arg1[0] | arg1[1] | arg1[2] | ...
+	 * points  | self    | arg2[0] | arg2[1] | ...
+	 */
+	long i, num;
+	VALUE bns_tmp, tmp_p, tmp_b;
+	const EC_POINT **points;
+	const BIGNUM **bignums;
+
+	Check_Type(arg1, T_ARRAY);
+	Check_Type(arg2, T_ARRAY);
+	if (RARRAY_LEN(arg1) != RARRAY_LEN(arg2) + 1) /* arg2 must be 1 larger */
+	    ossl_raise(rb_eArgError, "bns must be 1 longer than points; see the documentation");
+
+	num = RARRAY_LEN(arg1);
+	bns_tmp = rb_ary_tmp_new(num);
+	bignums = ALLOCV_N(const BIGNUM *, tmp_b, num);
+	for (i = 0; i < num; i++) {
+	    VALUE item = RARRAY_AREF(arg1, i);
+	    bignums[i] = GetBNPtr(item);
+	    rb_ary_push(bns_tmp, item);
+	}
+
+	points = ALLOCV_N(const EC_POINT *, tmp_p, num);
+	points[0] = point_self; /* self */
+	for (i = 0; i < num - 1; i++)
+	    SafeGetECPoint(RARRAY_AREF(arg2, i), points[i + 1]);
+
+	if (!NIL_P(arg3))
+	    bn_g = GetBNPtr(arg3);
+
+	if (EC_POINTs_mul(group, point_result, bn_g, num, points, bignums, ossl_bn_ctx) != 1) {
+	    ALLOCV_END(tmp_b);
+	    ALLOCV_END(tmp_p);
+	    ossl_raise(eEC_POINT, NULL);
+	}
+
+	ALLOCV_END(tmp_b);
+	ALLOCV_END(tmp_p);
+    }
+
+    return result;
+}
+
+void Init_ossl_ec(void)
+{
+#if 0
+    mPKey = rb_define_module_under(mOSSL, "PKey");
+    cPKey = rb_define_class_under(mPKey, "PKey", rb_cObject);
+    eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError);
+    ePKeyError = rb_define_class_under(mPKey, "PKeyError", eOSSLError);
+#endif
+
+    eECError = rb_define_class_under(mPKey, "ECError", ePKeyError);
+
+    /*
+     * Document-class: OpenSSL::PKey::EC
+     *
+     * OpenSSL::PKey::EC provides access to Elliptic Curve Digital Signature
+     * Algorithm (ECDSA) and Elliptic Curve Diffie-Hellman (ECDH).
+     *
+     * === Key exchange
+     *   ec1 = OpenSSL::PKey::EC.generate("prime256v1")
+     *   ec2 = OpenSSL::PKey::EC.generate("prime256v1")
+     *   # ec1 and ec2 have own private key respectively
+     *   shared_key1 = ec1.dh_compute_key(ec2.public_key)
+     *   shared_key2 = ec2.dh_compute_key(ec1.public_key)
+     *
+     *   p shared_key1 == shared_key2 #=> true
+     */
+    cEC = rb_define_class_under(mPKey, "EC", cPKey);
+    cEC_GROUP = rb_define_class_under(cEC, "Group", rb_cObject);
+    cEC_POINT = rb_define_class_under(cEC, "Point", rb_cObject);
+    eEC_GROUP = rb_define_class_under(cEC_GROUP, "Error", eOSSLError);
+    eEC_POINT = rb_define_class_under(cEC_POINT, "Error", eOSSLError);
+
+    s_GFp = rb_intern("GFp");
+    s_GF2m = rb_intern("GF2m");
+    s_GFp_simple = rb_intern("GFp_simple");
+    s_GFp_mont = rb_intern("GFp_mont");
+    s_GFp_nist = rb_intern("GFp_nist");
+    s_GF2m_simple = rb_intern("GF2m_simple");
+
+    ID_uncompressed = rb_intern("uncompressed");
+    ID_compressed = rb_intern("compressed");
+    ID_hybrid = rb_intern("hybrid");
+
+    rb_define_const(cEC, "NAMED_CURVE", INT2NUM(OPENSSL_EC_NAMED_CURVE));
+#if defined(OPENSSL_EC_EXPLICIT_CURVE)
+    rb_define_const(cEC, "EXPLICIT_CURVE", INT2NUM(OPENSSL_EC_EXPLICIT_CURVE));
+#endif
+
+    rb_define_singleton_method(cEC, "builtin_curves", ossl_s_builtin_curves, 0);
+
+    rb_define_singleton_method(cEC, "generate", ossl_ec_key_s_generate, 1);
+    rb_define_method(cEC, "initialize", ossl_ec_key_initialize, -1);
+    rb_define_copy_func(cEC, ossl_ec_key_initialize_copy);
+/* copy/dup/cmp */
+
+    rb_define_method(cEC, "group", ossl_ec_key_get_group, 0);
+    rb_define_method(cEC, "group=", ossl_ec_key_set_group, 1);
+    rb_define_method(cEC, "private_key", ossl_ec_key_get_private_key, 0);
+    rb_define_method(cEC, "private_key=", ossl_ec_key_set_private_key, 1);
+    rb_define_method(cEC, "public_key", ossl_ec_key_get_public_key, 0);
+    rb_define_method(cEC, "public_key=", ossl_ec_key_set_public_key, 1);
+    rb_define_method(cEC, "private?", ossl_ec_key_is_private, 0);
+    rb_define_method(cEC, "public?", ossl_ec_key_is_public, 0);
+    rb_define_alias(cEC, "private_key?", "private?");
+    rb_define_alias(cEC, "public_key?", "public?");
+/*  rb_define_method(cEC, "", ossl_ec_key_get_, 0);
+    rb_define_method(cEC, "=", ossl_ec_key_set_ 1);
+    set/get enc_flags
+    set/get _conv_from
+    set/get asn1_flag (can use ruby to call self.group.asn1_flag)
+    set/get precompute_mult
+*/
+    rb_define_method(cEC, "generate_key!", ossl_ec_key_generate_key, 0);
+    rb_define_alias(cEC, "generate_key", "generate_key!");
+    rb_define_method(cEC, "check_key", ossl_ec_key_check_key, 0);
+
+    rb_define_method(cEC, "dh_compute_key", ossl_ec_key_dh_compute_key, 1);
+    rb_define_method(cEC, "dsa_sign_asn1", ossl_ec_key_dsa_sign_asn1, 1);
+    rb_define_method(cEC, "dsa_verify_asn1", ossl_ec_key_dsa_verify_asn1, 2);
+/* do_sign/do_verify */
+
+    rb_define_method(cEC, "export", ossl_ec_key_export, -1);
+    rb_define_alias(cEC, "to_pem", "export");
+    rb_define_method(cEC, "to_der", ossl_ec_key_to_der, 0);
+    rb_define_method(cEC, "to_text", ossl_ec_key_to_text, 0);
+
+
+    rb_define_alloc_func(cEC_GROUP, ossl_ec_group_alloc);
+    rb_define_method(cEC_GROUP, "initialize", ossl_ec_group_initialize, -1);
+    rb_define_copy_func(cEC_GROUP, ossl_ec_group_initialize_copy);
+    rb_define_method(cEC_GROUP, "eql?", ossl_ec_group_eql, 1);
+    rb_define_alias(cEC_GROUP, "==", "eql?");
+/* copy/dup/cmp */
+
+    rb_define_method(cEC_GROUP, "generator", ossl_ec_group_get_generator, 0);
+    rb_define_method(cEC_GROUP, "set_generator", ossl_ec_group_set_generator, 3);
+    rb_define_method(cEC_GROUP, "order", ossl_ec_group_get_order, 0);
+    rb_define_method(cEC_GROUP, "cofactor", ossl_ec_group_get_cofactor, 0);
+
+    rb_define_method(cEC_GROUP, "curve_name", ossl_ec_group_get_curve_name, 0);
+/*    rb_define_method(cEC_GROUP, "curve_name=", ossl_ec_group_set_curve_name, 1); */
+
+    rb_define_method(cEC_GROUP, "asn1_flag", ossl_ec_group_get_asn1_flag, 0);
+    rb_define_method(cEC_GROUP, "asn1_flag=", ossl_ec_group_set_asn1_flag, 1);
+
+    rb_define_method(cEC_GROUP, "point_conversion_form", ossl_ec_group_get_point_conversion_form, 0);
+    rb_define_method(cEC_GROUP, "point_conversion_form=", ossl_ec_group_set_point_conversion_form, 1);
+
+    rb_define_method(cEC_GROUP, "seed", ossl_ec_group_get_seed, 0);
+    rb_define_method(cEC_GROUP, "seed=", ossl_ec_group_set_seed, 1);
+
+/* get/set GFp, GF2m */
+
+    rb_define_method(cEC_GROUP, "degree", ossl_ec_group_get_degree, 0);
+
+/* check* */
+
+
+    rb_define_method(cEC_GROUP, "to_pem", ossl_ec_group_to_pem, 0);
+    rb_define_method(cEC_GROUP, "to_der", ossl_ec_group_to_der, 0);
+    rb_define_method(cEC_GROUP, "to_text", ossl_ec_group_to_text, 0);
+
+
+    rb_define_alloc_func(cEC_POINT, ossl_ec_point_alloc);
+    rb_define_method(cEC_POINT, "initialize", ossl_ec_point_initialize, -1);
+    rb_define_copy_func(cEC_POINT, ossl_ec_point_initialize_copy);
+    rb_attr(cEC_POINT, rb_intern("group"), 1, 0, 0);
+    rb_define_method(cEC_POINT, "eql?", ossl_ec_point_eql, 1);
+    rb_define_alias(cEC_POINT, "==", "eql?");
+
+    rb_define_method(cEC_POINT, "infinity?", ossl_ec_point_is_at_infinity, 0);
+    rb_define_method(cEC_POINT, "on_curve?", ossl_ec_point_is_on_curve, 0);
+    rb_define_method(cEC_POINT, "make_affine!", ossl_ec_point_make_affine, 0);
+    rb_define_method(cEC_POINT, "invert!", ossl_ec_point_invert, 0);
+    rb_define_method(cEC_POINT, "set_to_infinity!", ossl_ec_point_set_to_infinity, 0);
+/* all the other methods */
+
+    rb_define_method(cEC_POINT, "to_bn", ossl_ec_point_to_bn, -1);
+    rb_define_method(cEC_POINT, "mul", ossl_ec_point_mul, -1);
+
+    id_i_group = rb_intern("@group");
+}
+
+#else /* defined NO_EC */
+void Init_ossl_ec(void)
+{
+}
+#endif /* NO_EC */
diff --git a/ext/openssl/ossl_pkey_rsa.c b/ext/openssl/ossl_pkey_rsa.c
new file mode 100644
index 0000000..5aa09d0
--- /dev/null
+++ b/ext/openssl/ossl_pkey_rsa.c
@@ -0,0 +1,766 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2001-2002  Michal Rokos <m.rokos at sh.cvut.cz>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#include "ossl.h"
+
+#if !defined(OPENSSL_NO_RSA)
+
+#define GetPKeyRSA(obj, pkey) do { \
+    GetPKey((obj), (pkey)); \
+    if (EVP_PKEY_base_id(pkey) != EVP_PKEY_RSA) { /* PARANOIA? */ \
+	ossl_raise(rb_eRuntimeError, "THIS IS NOT A RSA!") ; \
+    } \
+} while (0)
+#define GetRSA(obj, rsa) do { \
+    EVP_PKEY *_pkey; \
+    GetPKeyRSA((obj), _pkey); \
+    (rsa) = EVP_PKEY_get0_RSA(_pkey); \
+} while (0)
+
+static inline int
+RSA_HAS_PRIVATE(RSA *rsa)
+{
+    const BIGNUM *p, *q;
+
+    RSA_get0_factors(rsa, &p, &q);
+    return p && q; /* d? why? */
+}
+
+static inline int
+RSA_PRIVATE(VALUE obj, RSA *rsa)
+{
+    return RSA_HAS_PRIVATE(rsa) || OSSL_PKEY_IS_PRIVATE(obj);
+}
+
+/*
+ * Classes
+ */
+VALUE cRSA;
+VALUE eRSAError;
+
+/*
+ * Public
+ */
+static VALUE
+rsa_instance(VALUE klass, RSA *rsa)
+{
+    EVP_PKEY *pkey;
+    VALUE obj;
+
+    if (!rsa) {
+	return Qfalse;
+    }
+    obj = NewPKey(klass);
+    if (!(pkey = EVP_PKEY_new())) {
+	return Qfalse;
+    }
+    if (!EVP_PKEY_assign_RSA(pkey, rsa)) {
+	EVP_PKEY_free(pkey);
+	return Qfalse;
+    }
+    SetPKey(obj, pkey);
+
+    return obj;
+}
+
+VALUE
+ossl_rsa_new(EVP_PKEY *pkey)
+{
+    VALUE obj;
+
+    if (!pkey) {
+	obj = rsa_instance(cRSA, RSA_new());
+    }
+    else {
+	obj = NewPKey(cRSA);
+	if (EVP_PKEY_base_id(pkey) != EVP_PKEY_RSA) {
+	    ossl_raise(rb_eTypeError, "Not a RSA key!");
+	}
+	SetPKey(obj, pkey);
+    }
+    if (obj == Qfalse) {
+	ossl_raise(eRSAError, NULL);
+    }
+
+    return obj;
+}
+
+/*
+ * Private
+ */
+struct rsa_blocking_gen_arg {
+    RSA *rsa;
+    BIGNUM *e;
+    int size;
+    BN_GENCB *cb;
+    int result;
+};
+
+static void *
+rsa_blocking_gen(void *arg)
+{
+    struct rsa_blocking_gen_arg *gen = (struct rsa_blocking_gen_arg *)arg;
+    gen->result = RSA_generate_key_ex(gen->rsa, gen->size, gen->e, gen->cb);
+    return 0;
+}
+
+static RSA *
+rsa_generate(int size, unsigned long exp)
+{
+    int i;
+    struct ossl_generate_cb_arg cb_arg = { 0 };
+    struct rsa_blocking_gen_arg gen_arg;
+    RSA *rsa = RSA_new();
+    BIGNUM *e = BN_new();
+    BN_GENCB *cb = BN_GENCB_new();
+
+    if (!rsa || !e || !cb) {
+	RSA_free(rsa);
+	BN_free(e);
+	BN_GENCB_free(cb);
+	return NULL;
+    }
+    for (i = 0; i < (int)sizeof(exp) * 8; ++i) {
+	if (exp & (1UL << i)) {
+	    if (BN_set_bit(e, i) == 0) {
+		BN_free(e);
+		RSA_free(rsa);
+		BN_GENCB_free(cb);
+		return NULL;
+	    }
+	}
+    }
+
+    if (rb_block_given_p())
+	cb_arg.yield = 1;
+    BN_GENCB_set(cb, ossl_generate_cb_2, &cb_arg);
+    gen_arg.rsa = rsa;
+    gen_arg.e = e;
+    gen_arg.size = size;
+    gen_arg.cb = cb;
+    if (cb_arg.yield == 1) {
+	/* we cannot release GVL when callback proc is supplied */
+	rsa_blocking_gen(&gen_arg);
+    } else {
+	/* there's a chance to unblock */
+	rb_thread_call_without_gvl(rsa_blocking_gen, &gen_arg, ossl_generate_cb_stop, &cb_arg);
+    }
+
+    BN_GENCB_free(cb);
+    BN_free(e);
+    if (!gen_arg.result) {
+	RSA_free(rsa);
+	if (cb_arg.state) {
+	    /* must clear OpenSSL error stack */
+	    ossl_clear_error();
+	    rb_jump_tag(cb_arg.state);
+	}
+	return NULL;
+    }
+
+    return rsa;
+}
+
+/*
+ * call-seq:
+ *   RSA.generate(size)           => RSA instance
+ *   RSA.generate(size, exponent) => RSA instance
+ *
+ * Generates an RSA keypair.  +size+ is an integer representing the desired key
+ * size.  Keys smaller than 1024 should be considered insecure.  +exponent+ is
+ * an odd number normally 3, 17, or 65537.
+ */
+static VALUE
+ossl_rsa_s_generate(int argc, VALUE *argv, VALUE klass)
+{
+/* why does this method exist?  why can't initialize take an optional exponent? */
+    RSA *rsa;
+    VALUE size, exp;
+    VALUE obj;
+
+    rb_scan_args(argc, argv, "11", &size, &exp);
+
+    rsa = rsa_generate(NUM2INT(size), NIL_P(exp) ? RSA_F4 : NUM2ULONG(exp)); /* err handled by rsa_instance */
+    obj = rsa_instance(klass, rsa);
+
+    if (obj == Qfalse) {
+	RSA_free(rsa);
+	ossl_raise(eRSAError, NULL);
+    }
+
+    return obj;
+}
+
+/*
+ * call-seq:
+ *   RSA.new(key_size)                 => RSA instance
+ *   RSA.new(encoded_key)              => RSA instance
+ *   RSA.new(encoded_key, pass_phrase) => RSA instance
+ *
+ * Generates or loads an RSA keypair.  If an integer +key_size+ is given it
+ * represents the desired key size.  Keys less than 1024 bits should be
+ * considered insecure.
+ *
+ * A key can instead be loaded from an +encoded_key+ which must be PEM or DER
+ * encoded.  A +pass_phrase+ can be used to decrypt the key.  If none is given
+ * OpenSSL will prompt for the pass phrase.
+ *
+ * = Examples
+ *
+ *   OpenSSL::PKey::RSA.new 2048
+ *   OpenSSL::PKey::RSA.new File.read 'rsa.pem'
+ *   OpenSSL::PKey::RSA.new File.read('rsa.pem'), 'my pass phrase'
+ */
+static VALUE
+ossl_rsa_initialize(int argc, VALUE *argv, VALUE self)
+{
+    EVP_PKEY *pkey;
+    RSA *rsa;
+    BIO *in;
+    VALUE arg, pass;
+
+    GetPKey(self, pkey);
+    if(rb_scan_args(argc, argv, "02", &arg, &pass) == 0) {
+	rsa = RSA_new();
+    }
+    else if (RB_INTEGER_TYPE_P(arg)) {
+	rsa = rsa_generate(NUM2INT(arg), NIL_P(pass) ? RSA_F4 : NUM2ULONG(pass));
+	if (!rsa) ossl_raise(eRSAError, NULL);
+    }
+    else {
+	pass = ossl_pem_passwd_value(pass);
+	arg = ossl_to_der_if_possible(arg);
+	in = ossl_obj2bio(arg);
+	rsa = PEM_read_bio_RSAPrivateKey(in, NULL, ossl_pem_passwd_cb, (void *)pass);
+	if (!rsa) {
+	    OSSL_BIO_reset(in);
+	    rsa = PEM_read_bio_RSA_PUBKEY(in, NULL, NULL, NULL);
+	}
+	if (!rsa) {
+	    OSSL_BIO_reset(in);
+	    rsa = d2i_RSAPrivateKey_bio(in, NULL);
+	}
+	if (!rsa) {
+	    OSSL_BIO_reset(in);
+	    rsa = d2i_RSA_PUBKEY_bio(in, NULL);
+	}
+	if (!rsa) {
+	    OSSL_BIO_reset(in);
+	    rsa = PEM_read_bio_RSAPublicKey(in, NULL, NULL, NULL);
+	}
+	if (!rsa) {
+	    OSSL_BIO_reset(in);
+	    rsa = d2i_RSAPublicKey_bio(in, NULL);
+	}
+	BIO_free(in);
+	if (!rsa) {
+	    ossl_raise(eRSAError, "Neither PUB key nor PRIV key");
+	}
+    }
+    if (!EVP_PKEY_assign_RSA(pkey, rsa)) {
+	RSA_free(rsa);
+	ossl_raise(eRSAError, NULL);
+    }
+
+    return self;
+}
+
+static VALUE
+ossl_rsa_initialize_copy(VALUE self, VALUE other)
+{
+    EVP_PKEY *pkey;
+    RSA *rsa, *rsa_new;
+
+    GetPKey(self, pkey);
+    if (EVP_PKEY_base_id(pkey) != EVP_PKEY_NONE)
+	ossl_raise(eRSAError, "RSA already initialized");
+    GetRSA(other, rsa);
+
+    rsa_new = ASN1_dup((i2d_of_void *)i2d_RSAPrivateKey, (d2i_of_void *)d2i_RSAPrivateKey, (char *)rsa);
+    if (!rsa_new)
+	ossl_raise(eRSAError, "ASN1_dup");
+
+    EVP_PKEY_assign_RSA(pkey, rsa_new);
+
+    return self;
+}
+
+/*
+ * call-seq:
+ *   rsa.public? => true
+ *
+ * The return value is always true since every private key is also a public
+ * key.
+ */
+static VALUE
+ossl_rsa_is_public(VALUE self)
+{
+    RSA *rsa;
+
+    GetRSA(self, rsa);
+    /*
+     * This method should check for n and e.  BUG.
+     */
+    (void)rsa;
+    return Qtrue;
+}
+
+/*
+ * call-seq:
+ *   rsa.private? => true | false
+ *
+ * Does this keypair contain a private key?
+ */
+static VALUE
+ossl_rsa_is_private(VALUE self)
+{
+    RSA *rsa;
+
+    GetRSA(self, rsa);
+
+    return RSA_PRIVATE(self, rsa) ? Qtrue : Qfalse;
+}
+
+/*
+ * call-seq:
+ *   rsa.export([cipher, pass_phrase]) => PEM-format String
+ *   rsa.to_pem([cipher, pass_phrase]) => PEM-format String
+ *   rsa.to_s([cipher, pass_phrase]) => PEM-format String
+ *
+ * Outputs this keypair in PEM encoding.  If +cipher+ and +pass_phrase+ are
+ * given they will be used to encrypt the key.  +cipher+ must be an
+ * OpenSSL::Cipher instance.
+ */
+static VALUE
+ossl_rsa_export(int argc, VALUE *argv, VALUE self)
+{
+    RSA *rsa;
+    BIO *out;
+    const EVP_CIPHER *ciph = NULL;
+    VALUE cipher, pass, str;
+
+    GetRSA(self, rsa);
+
+    rb_scan_args(argc, argv, "02", &cipher, &pass);
+
+    if (!NIL_P(cipher)) {
+	ciph = GetCipherPtr(cipher);
+	pass = ossl_pem_passwd_value(pass);
+    }
+    if (!(out = BIO_new(BIO_s_mem()))) {
+	ossl_raise(eRSAError, NULL);
+    }
+    if (RSA_HAS_PRIVATE(rsa)) {
+	if (!PEM_write_bio_RSAPrivateKey(out, rsa, ciph, NULL, 0,
+					 ossl_pem_passwd_cb, (void *)pass)) {
+	    BIO_free(out);
+	    ossl_raise(eRSAError, NULL);
+	}
+    } else {
+	if (!PEM_write_bio_RSA_PUBKEY(out, rsa)) {
+	    BIO_free(out);
+	    ossl_raise(eRSAError, NULL);
+	}
+    }
+    str = ossl_membio2str(out);
+
+    return str;
+}
+
+/*
+ * call-seq:
+ *   rsa.to_der => DER-format String
+ *
+ * Outputs this keypair in DER encoding.
+ */
+static VALUE
+ossl_rsa_to_der(VALUE self)
+{
+    RSA *rsa;
+    int (*i2d_func)(const RSA *, unsigned char **);
+    unsigned char *p;
+    long len;
+    VALUE str;
+
+    GetRSA(self, rsa);
+    if (RSA_HAS_PRIVATE(rsa))
+	i2d_func = i2d_RSAPrivateKey;
+    else
+	i2d_func = (int (*)(const RSA *, unsigned char **))i2d_RSA_PUBKEY;
+    if((len = i2d_func(rsa, NULL)) <= 0)
+	ossl_raise(eRSAError, NULL);
+    str = rb_str_new(0, len);
+    p = (unsigned char *)RSTRING_PTR(str);
+    if(i2d_func(rsa, &p) < 0)
+	ossl_raise(eRSAError, NULL);
+    ossl_str_adjust(str, p);
+
+    return str;
+}
+
+/*
+ * call-seq:
+ *   rsa.public_encrypt(string)          => String
+ *   rsa.public_encrypt(string, padding) => String
+ *
+ * Encrypt +string+ with the public key.  +padding+ defaults to PKCS1_PADDING.
+ * The encrypted string output can be decrypted using #private_decrypt.
+ */
+static VALUE
+ossl_rsa_public_encrypt(int argc, VALUE *argv, VALUE self)
+{
+    RSA *rsa;
+    const BIGNUM *rsa_n;
+    int buf_len, pad;
+    VALUE str, buffer, padding;
+
+    GetRSA(self, rsa);
+    RSA_get0_key(rsa, &rsa_n, NULL, NULL);
+    if (!rsa_n)
+	ossl_raise(eRSAError, "incomplete RSA");
+    rb_scan_args(argc, argv, "11", &buffer, &padding);
+    pad = (argc == 1) ? RSA_PKCS1_PADDING : NUM2INT(padding);
+    StringValue(buffer);
+    str = rb_str_new(0, RSA_size(rsa));
+    buf_len = RSA_public_encrypt(RSTRING_LENINT(buffer), (unsigned char *)RSTRING_PTR(buffer),
+				 (unsigned char *)RSTRING_PTR(str), rsa, pad);
+    if (buf_len < 0) ossl_raise(eRSAError, NULL);
+    rb_str_set_len(str, buf_len);
+
+    return str;
+}
+
+/*
+ * call-seq:
+ *   rsa.public_decrypt(string)          => String
+ *   rsa.public_decrypt(string, padding) => String
+ *
+ * Decrypt +string+, which has been encrypted with the private key, with the
+ * public key.  +padding+ defaults to PKCS1_PADDING.
+ */
+static VALUE
+ossl_rsa_public_decrypt(int argc, VALUE *argv, VALUE self)
+{
+    RSA *rsa;
+    const BIGNUM *rsa_n;
+    int buf_len, pad;
+    VALUE str, buffer, padding;
+
+    GetRSA(self, rsa);
+    RSA_get0_key(rsa, &rsa_n, NULL, NULL);
+    if (!rsa_n)
+	ossl_raise(eRSAError, "incomplete RSA");
+    rb_scan_args(argc, argv, "11", &buffer, &padding);
+    pad = (argc == 1) ? RSA_PKCS1_PADDING : NUM2INT(padding);
+    StringValue(buffer);
+    str = rb_str_new(0, RSA_size(rsa));
+    buf_len = RSA_public_decrypt(RSTRING_LENINT(buffer), (unsigned char *)RSTRING_PTR(buffer),
+				 (unsigned char *)RSTRING_PTR(str), rsa, pad);
+    if (buf_len < 0) ossl_raise(eRSAError, NULL);
+    rb_str_set_len(str, buf_len);
+
+    return str;
+}
+
+/*
+ * call-seq:
+ *   rsa.private_encrypt(string)          => String
+ *   rsa.private_encrypt(string, padding) => String
+ *
+ * Encrypt +string+ with the private key.  +padding+ defaults to PKCS1_PADDING.
+ * The encrypted string output can be decrypted using #public_decrypt.
+ */
+static VALUE
+ossl_rsa_private_encrypt(int argc, VALUE *argv, VALUE self)
+{
+    RSA *rsa;
+    const BIGNUM *rsa_n;
+    int buf_len, pad;
+    VALUE str, buffer, padding;
+
+    GetRSA(self, rsa);
+    RSA_get0_key(rsa, &rsa_n, NULL, NULL);
+    if (!rsa_n)
+	ossl_raise(eRSAError, "incomplete RSA");
+    if (!RSA_PRIVATE(self, rsa))
+	ossl_raise(eRSAError, "private key needed.");
+    rb_scan_args(argc, argv, "11", &buffer, &padding);
+    pad = (argc == 1) ? RSA_PKCS1_PADDING : NUM2INT(padding);
+    StringValue(buffer);
+    str = rb_str_new(0, RSA_size(rsa));
+    buf_len = RSA_private_encrypt(RSTRING_LENINT(buffer), (unsigned char *)RSTRING_PTR(buffer),
+				  (unsigned char *)RSTRING_PTR(str), rsa, pad);
+    if (buf_len < 0) ossl_raise(eRSAError, NULL);
+    rb_str_set_len(str, buf_len);
+
+    return str;
+}
+
+/*
+ * call-seq:
+ *   rsa.private_decrypt(string)          => String
+ *   rsa.private_decrypt(string, padding) => String
+ *
+ * Decrypt +string+, which has been encrypted with the public key, with the
+ * private key.  +padding+ defaults to PKCS1_PADDING.
+ */
+static VALUE
+ossl_rsa_private_decrypt(int argc, VALUE *argv, VALUE self)
+{
+    RSA *rsa;
+    const BIGNUM *rsa_n;
+    int buf_len, pad;
+    VALUE str, buffer, padding;
+
+    GetRSA(self, rsa);
+    RSA_get0_key(rsa, &rsa_n, NULL, NULL);
+    if (!rsa_n)
+	ossl_raise(eRSAError, "incomplete RSA");
+    if (!RSA_PRIVATE(self, rsa))
+	ossl_raise(eRSAError, "private key needed.");
+    rb_scan_args(argc, argv, "11", &buffer, &padding);
+    pad = (argc == 1) ? RSA_PKCS1_PADDING : NUM2INT(padding);
+    StringValue(buffer);
+    str = rb_str_new(0, RSA_size(rsa));
+    buf_len = RSA_private_decrypt(RSTRING_LENINT(buffer), (unsigned char *)RSTRING_PTR(buffer),
+				  (unsigned char *)RSTRING_PTR(str), rsa, pad);
+    if (buf_len < 0) ossl_raise(eRSAError, NULL);
+    rb_str_set_len(str, buf_len);
+
+    return str;
+}
+
+/*
+ * call-seq:
+ *   rsa.params => hash
+ *
+ * THIS METHOD IS INSECURE, PRIVATE INFORMATION CAN LEAK OUT!!!
+ *
+ * Stores all parameters of key to the hash.  The hash has keys 'n', 'e', 'd',
+ * 'p', 'q', 'dmp1', 'dmq1', 'iqmp'.
+ *
+ * Don't use :-)) (It's up to you)
+ */
+static VALUE
+ossl_rsa_get_params(VALUE self)
+{
+    RSA *rsa;
+    VALUE hash;
+    const BIGNUM *n, *e, *d, *p, *q, *dmp1, *dmq1, *iqmp;
+
+    GetRSA(self, rsa);
+    RSA_get0_key(rsa, &n, &e, &d);
+    RSA_get0_factors(rsa, &p, &q);
+    RSA_get0_crt_params(rsa, &dmp1, &dmq1, &iqmp);
+
+    hash = rb_hash_new();
+    rb_hash_aset(hash, rb_str_new2("n"), ossl_bn_new(n));
+    rb_hash_aset(hash, rb_str_new2("e"), ossl_bn_new(e));
+    rb_hash_aset(hash, rb_str_new2("d"), ossl_bn_new(d));
+    rb_hash_aset(hash, rb_str_new2("p"), ossl_bn_new(p));
+    rb_hash_aset(hash, rb_str_new2("q"), ossl_bn_new(q));
+    rb_hash_aset(hash, rb_str_new2("dmp1"), ossl_bn_new(dmp1));
+    rb_hash_aset(hash, rb_str_new2("dmq1"), ossl_bn_new(dmq1));
+    rb_hash_aset(hash, rb_str_new2("iqmp"), ossl_bn_new(iqmp));
+
+    return hash;
+}
+
+/*
+ * call-seq:
+ *   rsa.to_text => String
+ *
+ * THIS METHOD IS INSECURE, PRIVATE INFORMATION CAN LEAK OUT!!!
+ *
+ * Dumps all parameters of a keypair to a String
+ *
+ * Don't use :-)) (It's up to you)
+ */
+static VALUE
+ossl_rsa_to_text(VALUE self)
+{
+    RSA *rsa;
+    BIO *out;
+    VALUE str;
+
+    GetRSA(self, rsa);
+    if (!(out = BIO_new(BIO_s_mem()))) {
+	ossl_raise(eRSAError, NULL);
+    }
+    if (!RSA_print(out, rsa, 0)) { /* offset = 0 */
+	BIO_free(out);
+	ossl_raise(eRSAError, NULL);
+    }
+    str = ossl_membio2str(out);
+
+    return str;
+}
+
+/*
+ * call-seq:
+ *    rsa.public_key -> RSA
+ *
+ * Makes new RSA instance containing the public key from the private key.
+ */
+static VALUE
+ossl_rsa_to_public_key(VALUE self)
+{
+    EVP_PKEY *pkey;
+    RSA *rsa;
+    VALUE obj;
+
+    GetPKeyRSA(self, pkey);
+    /* err check performed by rsa_instance */
+    rsa = RSAPublicKey_dup(EVP_PKEY_get0_RSA(pkey));
+    obj = rsa_instance(rb_obj_class(self), rsa);
+    if (obj == Qfalse) {
+	RSA_free(rsa);
+	ossl_raise(eRSAError, NULL);
+    }
+    return obj;
+}
+
+/*
+ * TODO: Test me
+
+static VALUE
+ossl_rsa_blinding_on(VALUE self)
+{
+    RSA *rsa;
+
+    GetRSA(self, rsa);
+
+    if (RSA_blinding_on(rsa, ossl_bn_ctx) != 1) {
+	ossl_raise(eRSAError, NULL);
+    }
+    return self;
+}
+
+static VALUE
+ossl_rsa_blinding_off(VALUE self)
+{
+    RSA *rsa;
+
+    GetRSA(self, rsa);
+    RSA_blinding_off(rsa);
+
+    return self;
+}
+ */
+
+/*
+ * Document-method: OpenSSL::PKey::RSA#set_key
+ * call-seq:
+ *   rsa.set_key(n, e, d) -> self
+ *
+ * Sets +n+, +e+, +d+ for the RSA instance.
+ */
+OSSL_PKEY_BN_DEF3(rsa, RSA, key, n, e, d)
+/*
+ * Document-method: OpenSSL::PKey::RSA#set_factors
+ * call-seq:
+ *   rsa.set_factors(p, q) -> self
+ *
+ * Sets +p+, +q+ for the RSA instance.
+ */
+OSSL_PKEY_BN_DEF2(rsa, RSA, factors, p, q)
+/*
+ * Document-method: OpenSSL::PKey::RSA#set_crt_params
+ * call-seq:
+ *   rsa.set_crt_params(dmp1, dmq1, iqmp) -> self
+ *
+ * Sets +dmp1+, +dmq1+, +iqmp+ for the RSA instance. They are calculated by
+ * <tt>d mod (p - 1)</tt>, <tt>d mod (q - 1)</tt> and <tt>q^(-1) mod p</tt>
+ * respectively.
+ */
+OSSL_PKEY_BN_DEF3(rsa, RSA, crt_params, dmp1, dmq1, iqmp)
+
+/*
+ * INIT
+ */
+#define DefRSAConst(x) rb_define_const(cRSA, #x, INT2NUM(RSA_##x))
+
+void
+Init_ossl_rsa(void)
+{
+#if 0
+    mPKey = rb_define_module_under(mOSSL, "PKey");
+    cPKey = rb_define_class_under(mPKey, "PKey", rb_cObject);
+    ePKeyError = rb_define_class_under(mPKey, "PKeyError", eOSSLError);
+#endif
+
+    /* Document-class: OpenSSL::PKey::RSAError
+     *
+     * Generic exception that is raised if an operation on an RSA PKey
+     * fails unexpectedly or in case an instantiation of an instance of RSA
+     * fails due to non-conformant input data.
+     */
+    eRSAError = rb_define_class_under(mPKey, "RSAError", ePKeyError);
+
+    /* Document-class: OpenSSL::PKey::RSA
+     *
+     * RSA is an asymmetric public key algorithm that has been formalized in
+     * RFC 3447. It is in widespread use in public key infrastructures (PKI)
+     * where certificates (cf. OpenSSL::X509::Certificate) often are issued
+     * on the basis of a public/private RSA key pair. RSA is used in a wide
+     * field of applications such as secure (symmetric) key exchange, e.g.
+     * when establishing a secure TLS/SSL connection. It is also used in
+     * various digital signature schemes.
+     */
+    cRSA = rb_define_class_under(mPKey, "RSA", cPKey);
+
+    rb_define_singleton_method(cRSA, "generate", ossl_rsa_s_generate, -1);
+    rb_define_method(cRSA, "initialize", ossl_rsa_initialize, -1);
+    rb_define_copy_func(cRSA, ossl_rsa_initialize_copy);
+
+    rb_define_method(cRSA, "public?", ossl_rsa_is_public, 0);
+    rb_define_method(cRSA, "private?", ossl_rsa_is_private, 0);
+    rb_define_method(cRSA, "to_text", ossl_rsa_to_text, 0);
+    rb_define_method(cRSA, "export", ossl_rsa_export, -1);
+    rb_define_alias(cRSA, "to_pem", "export");
+    rb_define_alias(cRSA, "to_s", "export");
+    rb_define_method(cRSA, "to_der", ossl_rsa_to_der, 0);
+    rb_define_method(cRSA, "public_key", ossl_rsa_to_public_key, 0);
+    rb_define_method(cRSA, "public_encrypt", ossl_rsa_public_encrypt, -1);
+    rb_define_method(cRSA, "public_decrypt", ossl_rsa_public_decrypt, -1);
+    rb_define_method(cRSA, "private_encrypt", ossl_rsa_private_encrypt, -1);
+    rb_define_method(cRSA, "private_decrypt", ossl_rsa_private_decrypt, -1);
+
+    DEF_OSSL_PKEY_BN(cRSA, rsa, n);
+    DEF_OSSL_PKEY_BN(cRSA, rsa, e);
+    DEF_OSSL_PKEY_BN(cRSA, rsa, d);
+    DEF_OSSL_PKEY_BN(cRSA, rsa, p);
+    DEF_OSSL_PKEY_BN(cRSA, rsa, q);
+    DEF_OSSL_PKEY_BN(cRSA, rsa, dmp1);
+    DEF_OSSL_PKEY_BN(cRSA, rsa, dmq1);
+    DEF_OSSL_PKEY_BN(cRSA, rsa, iqmp);
+    rb_define_method(cRSA, "set_key", ossl_rsa_set_key, 3);
+    rb_define_method(cRSA, "set_factors", ossl_rsa_set_factors, 2);
+    rb_define_method(cRSA, "set_crt_params", ossl_rsa_set_crt_params, 3);
+
+    rb_define_method(cRSA, "params", ossl_rsa_get_params, 0);
+
+    DefRSAConst(PKCS1_PADDING);
+    DefRSAConst(SSLV23_PADDING);
+    DefRSAConst(NO_PADDING);
+    DefRSAConst(PKCS1_OAEP_PADDING);
+
+/*
+ * TODO: Test it
+    rb_define_method(cRSA, "blinding_on!", ossl_rsa_blinding_on, 0);
+    rb_define_method(cRSA, "blinding_off!", ossl_rsa_blinding_off, 0);
+ */
+}
+
+#else /* defined NO_RSA */
+void
+Init_ossl_rsa(void)
+{
+}
+#endif /* NO_RSA */
diff --git a/ext/openssl/ossl_rand.c b/ext/openssl/ossl_rand.c
new file mode 100644
index 0000000..688c525
--- /dev/null
+++ b/ext/openssl/ossl_rand.c
@@ -0,0 +1,238 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2001-2002  Michal Rokos <m.rokos at sh.cvut.cz>
+ *
+ * All rights reserved.
+ *
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#include "ossl.h"
+
+VALUE mRandom;
+VALUE eRandomError;
+
+/*
+ *  call-seq:
+ *     seed(str) -> str
+ *
+ * ::seed is equivalent to ::add where +entropy+ is length of +str+.
+ */
+static VALUE
+ossl_rand_seed(VALUE self, VALUE str)
+{
+    StringValue(str);
+    RAND_seed(RSTRING_PTR(str), RSTRING_LENINT(str));
+
+    return str;
+}
+
+/*
+ *  call-seq:
+ *     add(str, entropy) -> self
+ *
+ * Mixes the bytes from +str+ into the Pseudo Random Number Generator(PRNG)
+ * state.
+ *
+ * Thus, if the data from +str+ are unpredictable to an adversary, this
+ * increases the uncertainty about the state and makes the PRNG output less
+ * predictable.
+ *
+ * The +entropy+ argument is (the lower bound of) an estimate of how much
+ * randomness is contained in +str+, measured in bytes.
+ *
+ * === Example
+ *
+ *    pid = $$
+ *    now = Time.now
+ *    ary = [now.to_i, now.nsec, 1000, pid]
+ *    OpenSSL::Random.add(ary.join, 0.0)
+ *    OpenSSL::Random.seed(ary.join)
+ */
+static VALUE
+ossl_rand_add(VALUE self, VALUE str, VALUE entropy)
+{
+    StringValue(str);
+    RAND_add(RSTRING_PTR(str), RSTRING_LENINT(str), NUM2DBL(entropy));
+
+    return self;
+}
+
+/*
+ *  call-seq:
+ *     load_random_file(filename) -> true
+ *
+ * Reads bytes from +filename+ and adds them to the PRNG.
+ */
+static VALUE
+ossl_rand_load_file(VALUE self, VALUE filename)
+{
+    rb_check_safe_obj(filename);
+
+    if(!RAND_load_file(StringValueCStr(filename), -1)) {
+	ossl_raise(eRandomError, NULL);
+    }
+    return Qtrue;
+}
+
+/*
+ *  call-seq:
+ *     write_random_file(filename) -> true
+ *
+ * Writes a number of random generated bytes (currently 1024) to +filename+
+ * which can be used to initialize the PRNG by calling ::load_random_file in a
+ * later session.
+ */
+static VALUE
+ossl_rand_write_file(VALUE self, VALUE filename)
+{
+    rb_check_safe_obj(filename);
+
+    if (RAND_write_file(StringValueCStr(filename)) == -1) {
+	ossl_raise(eRandomError, NULL);
+    }
+    return Qtrue;
+}
+
+/*
+ *  call-seq:
+ *	random_bytes(length) -> string
+ *
+ * Generates +string+ with +length+ number of cryptographically strong
+ * pseudo-random bytes.
+ *
+ * === Example
+ *
+ *    OpenSSL::Random.random_bytes(12)
+ *    #=> "..."
+ */
+static VALUE
+ossl_rand_bytes(VALUE self, VALUE len)
+{
+    VALUE str;
+    int n = NUM2INT(len);
+    int ret;
+
+    str = rb_str_new(0, n);
+    ret = RAND_bytes((unsigned char *)RSTRING_PTR(str), n);
+    if (ret == 0) {
+	ossl_raise(eRandomError, "RAND_bytes");
+    } else if (ret == -1) {
+	ossl_raise(eRandomError, "RAND_bytes is not supported");
+    }
+
+    return str;
+}
+
+#if defined(HAVE_RAND_PSEUDO_BYTES)
+/*
+ *  call-seq:
+ *	pseudo_bytes(length) -> string
+ *
+ * Generates +string+ with +length+ number of pseudo-random bytes.
+ *
+ * Pseudo-random byte sequences generated by ::pseudo_bytes will be unique if
+ * they are of sufficient length, but are not necessarily unpredictable.
+ *
+ * === Example
+ *
+ *    OpenSSL::Random.pseudo_bytes(12)
+ *    #=> "..."
+ */
+static VALUE
+ossl_rand_pseudo_bytes(VALUE self, VALUE len)
+{
+    VALUE str;
+    int n = NUM2INT(len);
+
+    str = rb_str_new(0, n);
+    if (RAND_pseudo_bytes((unsigned char *)RSTRING_PTR(str), n) < 1) {
+	ossl_raise(eRandomError, NULL);
+    }
+
+    return str;
+}
+#endif
+
+#ifdef HAVE_RAND_EGD
+/*
+ *  call-seq:
+ *     egd(filename) -> true
+ *
+ * Same as ::egd_bytes but queries 255 bytes by default.
+ */
+static VALUE
+ossl_rand_egd(VALUE self, VALUE filename)
+{
+    rb_check_safe_obj(filename);
+
+    if (RAND_egd(StringValueCStr(filename)) == -1) {
+	ossl_raise(eRandomError, NULL);
+    }
+    return Qtrue;
+}
+
+/*
+ *  call-seq:
+ *     egd_bytes(filename, length) -> true
+ *
+ * Queries the entropy gathering daemon EGD on socket path given by +filename+.
+ *
+ * Fetches +length+ number of bytes and uses ::add to seed the OpenSSL built-in
+ * PRNG.
+ */
+static VALUE
+ossl_rand_egd_bytes(VALUE self, VALUE filename, VALUE len)
+{
+    int n = NUM2INT(len);
+
+    rb_check_safe_obj(filename);
+
+    if (RAND_egd_bytes(StringValueCStr(filename), n) == -1) {
+	ossl_raise(eRandomError, NULL);
+    }
+    return Qtrue;
+}
+#endif /* HAVE_RAND_EGD */
+
+/*
+ *  call-seq:
+ *     status? => true | false
+ *
+ * Return true if the PRNG has been seeded with enough data, false otherwise.
+ */
+static VALUE
+ossl_rand_status(VALUE self)
+{
+    return RAND_status() ? Qtrue : Qfalse;
+}
+
+/*
+ * INIT
+ */
+void
+Init_ossl_rand(void)
+{
+#if 0
+    mOSSL = rb_define_module("OpenSSL");
+    eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError);
+#endif
+
+    mRandom = rb_define_module_under(mOSSL, "Random");
+
+    eRandomError = rb_define_class_under(mRandom, "RandomError", eOSSLError);
+
+    rb_define_module_function(mRandom, "seed", ossl_rand_seed, 1);
+    rb_define_module_function(mRandom, "random_add", ossl_rand_add, 2);
+    rb_define_module_function(mRandom, "load_random_file", ossl_rand_load_file, 1);
+    rb_define_module_function(mRandom, "write_random_file", ossl_rand_write_file, 1);
+    rb_define_module_function(mRandom, "random_bytes", ossl_rand_bytes, 1);
+#if defined(HAVE_RAND_PSEUDO_BYTES)
+    rb_define_module_function(mRandom, "pseudo_bytes", ossl_rand_pseudo_bytes, 1);
+#endif
+#ifdef HAVE_RAND_EGD
+    rb_define_module_function(mRandom, "egd", ossl_rand_egd, 1);
+    rb_define_module_function(mRandom, "egd_bytes", ossl_rand_egd_bytes, 2);
+#endif /* HAVE_RAND_EGD */
+    rb_define_module_function(mRandom, "status?", ossl_rand_status, 0);
+}
diff --git a/ext/openssl/ossl_rand.h b/ext/openssl/ossl_rand.h
new file mode 100644
index 0000000..8f77a3b
--- /dev/null
+++ b/ext/openssl/ossl_rand.h
@@ -0,0 +1,18 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2001-2002  Michal Rokos <m.rokos at sh.cvut.cz>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#if !defined(_OSSL_RAND_H_)
+#define _OSSL_RAND_H_
+
+extern VALUE mRandom;
+extern VALUE eRandomError;
+
+void Init_ossl_rand(void);
+
+#endif /* _OSSL_RAND_H_ */
diff --git a/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c
new file mode 100644
index 0000000..e2c8eb5
--- /dev/null
+++ b/ext/openssl/ossl_ssl.c
@@ -0,0 +1,2751 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2000-2002  GOTOU Yuuzou <gotoyuzo at notwork.org>
+ * Copyright (C) 2001-2002  Michal Rokos <m.rokos at sh.cvut.cz>
+ * Copyright (C) 2001-2007  Technorama Ltd. <oss-ruby at technorama.net>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#include "ossl.h"
+
+#define numberof(ary) (int)(sizeof(ary)/sizeof((ary)[0]))
+
+#ifdef _WIN32
+#  define TO_SOCKET(s) _get_osfhandle(s)
+#else
+#  define TO_SOCKET(s) (s)
+#endif
+
+#define GetSSLCTX(obj, ctx) do { \
+	TypedData_Get_Struct((obj), SSL_CTX, &ossl_sslctx_type, (ctx));	\
+} while (0)
+
+VALUE mSSL;
+static VALUE mSSLExtConfig;
+static VALUE eSSLError;
+VALUE cSSLContext;
+VALUE cSSLSocket;
+
+static VALUE eSSLErrorWaitReadable;
+static VALUE eSSLErrorWaitWritable;
+
+static ID ID_callback_state, id_tmp_dh_callback, id_tmp_ecdh_callback,
+	  id_npn_protocols_encoded;
+static VALUE sym_exception, sym_wait_readable, sym_wait_writable;
+
+static ID id_i_cert_store, id_i_ca_file, id_i_ca_path, id_i_verify_mode,
+	  id_i_verify_depth, id_i_verify_callback, id_i_client_ca,
+	  id_i_renegotiation_cb, id_i_cert, id_i_key, id_i_extra_chain_cert,
+	  id_i_client_cert_cb, id_i_tmp_ecdh_callback, id_i_timeout,
+	  id_i_session_id_context, id_i_session_get_cb, id_i_session_new_cb,
+	  id_i_session_remove_cb, id_i_npn_select_cb, id_i_npn_protocols,
+	  id_i_alpn_select_cb, id_i_alpn_protocols, id_i_servername_cb,
+	  id_i_verify_hostname;
+static ID id_i_io, id_i_context, id_i_hostname;
+
+/*
+ * SSLContext class
+ */
+static const struct {
+    const char *name;
+    SSL_METHOD *(*func)(void); /* FIXME: constify when dropping 0.9.8 */
+    int version;
+} ossl_ssl_method_tab[] = {
+#if defined(HAVE_SSL_CTX_SET_MIN_PROTO_VERSION)
+#define OSSL_SSL_METHOD_ENTRY(name, version) \
+    { #name,          (SSL_METHOD *(*)(void))TLS_method, version }, \
+    { #name"_server", (SSL_METHOD *(*)(void))TLS_server_method, version }, \
+    { #name"_client", (SSL_METHOD *(*)(void))TLS_client_method, version }
+#else
+#define OSSL_SSL_METHOD_ENTRY(name, version) \
+    { #name,          (SSL_METHOD *(*)(void))name##_method, version }, \
+    { #name"_server", (SSL_METHOD *(*)(void))name##_server_method, version }, \
+    { #name"_client", (SSL_METHOD *(*)(void))name##_client_method, version }
+#endif
+#if defined(HAVE_SSLV2_METHOD)
+    OSSL_SSL_METHOD_ENTRY(SSLv2, SSL2_VERSION),
+#endif
+#if defined(HAVE_SSLV3_METHOD)
+    OSSL_SSL_METHOD_ENTRY(SSLv3, SSL3_VERSION),
+#endif
+    OSSL_SSL_METHOD_ENTRY(TLSv1, TLS1_VERSION),
+#if defined(HAVE_TLSV1_1_METHOD)
+    OSSL_SSL_METHOD_ENTRY(TLSv1_1, TLS1_1_VERSION),
+#endif
+#if defined(HAVE_TLSV1_2_METHOD)
+    OSSL_SSL_METHOD_ENTRY(TLSv1_2, TLS1_2_VERSION),
+#endif
+    OSSL_SSL_METHOD_ENTRY(SSLv23, 0),
+#undef OSSL_SSL_METHOD_ENTRY
+};
+
+static int ossl_ssl_ex_vcb_idx;
+static int ossl_ssl_ex_store_p;
+static int ossl_ssl_ex_ptr_idx;
+
+static void
+ossl_sslctx_free(void *ptr)
+{
+    SSL_CTX *ctx = ptr;
+#if !defined(HAVE_X509_STORE_UP_REF)
+    if(ctx && SSL_CTX_get_ex_data(ctx, ossl_ssl_ex_store_p)== (void*)1)
+	ctx->cert_store = NULL;
+#endif
+    SSL_CTX_free(ctx);
+}
+
+static const rb_data_type_t ossl_sslctx_type = {
+    "OpenSSL/SSL/CTX",
+    {
+	0, ossl_sslctx_free,
+    },
+    0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
+};
+
+static VALUE
+ossl_sslctx_s_alloc(VALUE klass)
+{
+    SSL_CTX *ctx;
+    long mode = SSL_MODE_ENABLE_PARTIAL_WRITE |
+	SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER;
+    VALUE obj;
+
+#ifdef SSL_MODE_RELEASE_BUFFERS
+    mode |= SSL_MODE_RELEASE_BUFFERS;
+#endif
+
+    obj = TypedData_Wrap_Struct(klass, &ossl_sslctx_type, 0);
+    ctx = SSL_CTX_new(SSLv23_method());
+    if (!ctx) {
+        ossl_raise(eSSLError, "SSL_CTX_new");
+    }
+    SSL_CTX_set_mode(ctx, mode);
+    RTYPEDDATA_DATA(obj) = ctx;
+    SSL_CTX_set_ex_data(ctx, ossl_ssl_ex_ptr_idx, (void*)obj);
+
+#if !defined(OPENSSL_NO_EC) && defined(HAVE_SSL_CTX_SET_ECDH_AUTO)
+    /* We use SSL_CTX_set1_curves_list() to specify the curve used in ECDH. It
+     * allows to specify multiple curve names and OpenSSL will select
+     * automatically from them. In OpenSSL 1.0.2, the automatic selection has to
+     * be enabled explicitly. But OpenSSL 1.1.0 removed the knob and it is
+     * always enabled. To uniform the behavior, we enable the automatic
+     * selection also in 1.0.2. Users can still disable ECDH by removing ECDH
+     * cipher suites by SSLContext#ciphers=. */
+    if (!SSL_CTX_set_ecdh_auto(ctx, 1))
+	ossl_raise(eSSLError, "SSL_CTX_set_ecdh_auto");
+#endif
+
+    return obj;
+}
+
+/*
+ * call-seq:
+ *    ctx.ssl_version = :TLSv1
+ *    ctx.ssl_version = "SSLv23_client"
+ *
+ * Sets the SSL/TLS protocol version for the context. This forces connections to
+ * use only the specified protocol version.
+ *
+ * You can get a list of valid versions with OpenSSL::SSL::SSLContext::METHODS
+ */
+static VALUE
+ossl_sslctx_set_ssl_version(VALUE self, VALUE ssl_method)
+{
+    SSL_CTX *ctx;
+    const char *s;
+    VALUE m = ssl_method;
+    int i;
+
+    GetSSLCTX(self, ctx);
+    if (RB_TYPE_P(ssl_method, T_SYMBOL))
+	m = rb_sym2str(ssl_method);
+    s = StringValueCStr(m);
+    for (i = 0; i < numberof(ossl_ssl_method_tab); i++) {
+        if (strcmp(ossl_ssl_method_tab[i].name, s) == 0) {
+#if defined(HAVE_SSL_CTX_SET_MIN_PROTO_VERSION)
+	    int version = ossl_ssl_method_tab[i].version;
+#endif
+	    SSL_METHOD *method = ossl_ssl_method_tab[i].func();
+
+	    if (SSL_CTX_set_ssl_version(ctx, method) != 1)
+		ossl_raise(eSSLError, "SSL_CTX_set_ssl_version");
+
+#if defined(HAVE_SSL_CTX_SET_MIN_PROTO_VERSION)
+	    if (!SSL_CTX_set_min_proto_version(ctx, version))
+		ossl_raise(eSSLError, "SSL_CTX_set_min_proto_version");
+	    if (!SSL_CTX_set_max_proto_version(ctx, version))
+		ossl_raise(eSSLError, "SSL_CTX_set_max_proto_version");
+#endif
+	    return ssl_method;
+        }
+    }
+
+    ossl_raise(rb_eArgError, "unknown SSL method `%"PRIsVALUE"'.", m);
+}
+
+static VALUE
+ossl_call_client_cert_cb(VALUE obj)
+{
+    VALUE ctx_obj, cb, ary, cert, key;
+
+    ctx_obj = rb_attr_get(obj, id_i_context);
+    cb = rb_attr_get(ctx_obj, id_i_client_cert_cb);
+    if (NIL_P(cb))
+	return Qnil;
+
+    ary = rb_funcall(cb, rb_intern("call"), 1, obj);
+    Check_Type(ary, T_ARRAY);
+    GetX509CertPtr(cert = rb_ary_entry(ary, 0));
+    GetPrivPKeyPtr(key = rb_ary_entry(ary, 1));
+
+    return rb_ary_new3(2, cert, key);
+}
+
+static int
+ossl_client_cert_cb(SSL *ssl, X509 **x509, EVP_PKEY **pkey)
+{
+    VALUE obj, ret;
+
+    obj = (VALUE)SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx);
+    ret = rb_protect(ossl_call_client_cert_cb, obj, NULL);
+    if (NIL_P(ret))
+	return 0;
+
+    *x509 = DupX509CertPtr(RARRAY_AREF(ret, 0));
+    *pkey = DupPKeyPtr(RARRAY_AREF(ret, 1));
+
+    return 1;
+}
+
+#if !defined(OPENSSL_NO_DH) || \
+    !defined(OPENSSL_NO_EC) && defined(HAVE_SSL_CTX_SET_TMP_ECDH_CALLBACK)
+struct tmp_dh_callback_args {
+    VALUE ssl_obj;
+    ID id;
+    int type;
+    int is_export;
+    int keylength;
+};
+
+static EVP_PKEY *
+ossl_call_tmp_dh_callback(struct tmp_dh_callback_args *args)
+{
+    VALUE cb, dh;
+    EVP_PKEY *pkey;
+
+    cb = rb_funcall(args->ssl_obj, args->id, 0);
+    if (NIL_P(cb))
+	return NULL;
+    dh = rb_funcall(cb, rb_intern("call"), 3,
+		    args->ssl_obj, INT2NUM(args->is_export), INT2NUM(args->keylength));
+    pkey = GetPKeyPtr(dh);
+    if (EVP_PKEY_base_id(pkey) != args->type)
+	return NULL;
+
+    return pkey;
+}
+#endif
+
+#if !defined(OPENSSL_NO_DH)
+static DH *
+ossl_tmp_dh_callback(SSL *ssl, int is_export, int keylength)
+{
+    VALUE rb_ssl;
+    EVP_PKEY *pkey;
+    struct tmp_dh_callback_args args;
+    int state;
+
+    rb_ssl = (VALUE)SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx);
+    args.ssl_obj = rb_ssl;
+    args.id = id_tmp_dh_callback;
+    args.is_export = is_export;
+    args.keylength = keylength;
+    args.type = EVP_PKEY_DH;
+
+    pkey = (EVP_PKEY *)rb_protect((VALUE (*)(VALUE))ossl_call_tmp_dh_callback,
+				  (VALUE)&args, &state);
+    if (state) {
+	rb_ivar_set(rb_ssl, ID_callback_state, INT2NUM(state));
+	return NULL;
+    }
+    if (!pkey)
+	return NULL;
+
+    return EVP_PKEY_get0_DH(pkey);
+}
+#endif /* OPENSSL_NO_DH */
+
+#if !defined(OPENSSL_NO_EC) && defined(HAVE_SSL_CTX_SET_TMP_ECDH_CALLBACK)
+static EC_KEY *
+ossl_tmp_ecdh_callback(SSL *ssl, int is_export, int keylength)
+{
+    VALUE rb_ssl;
+    EVP_PKEY *pkey;
+    struct tmp_dh_callback_args args;
+    int state;
+
+    rb_ssl = (VALUE)SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx);
+    args.ssl_obj = rb_ssl;
+    args.id = id_tmp_ecdh_callback;
+    args.is_export = is_export;
+    args.keylength = keylength;
+    args.type = EVP_PKEY_EC;
+
+    pkey = (EVP_PKEY *)rb_protect((VALUE (*)(VALUE))ossl_call_tmp_dh_callback,
+				  (VALUE)&args, &state);
+    if (state) {
+	rb_ivar_set(rb_ssl, ID_callback_state, INT2NUM(state));
+	return NULL;
+    }
+    if (!pkey)
+	return NULL;
+
+    return EVP_PKEY_get0_EC_KEY(pkey);
+}
+#endif
+
+static VALUE
+call_verify_certificate_identity(VALUE ctx_v)
+{
+    X509_STORE_CTX *ctx = (X509_STORE_CTX *)ctx_v;
+    SSL *ssl;
+    VALUE ssl_obj, hostname, cert_obj;
+
+    ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
+    ssl_obj = (VALUE)SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx);
+    hostname = rb_attr_get(ssl_obj, id_i_hostname);
+
+    if (!RTEST(hostname)) {
+	rb_warning("verify_hostname requires hostname to be set");
+	return Qtrue;
+    }
+
+    cert_obj = ossl_x509_new(X509_STORE_CTX_get_current_cert(ctx));
+    return rb_funcall(mSSL, rb_intern("verify_certificate_identity"), 2,
+		      cert_obj, hostname);
+}
+
+static int
+ossl_ssl_verify_callback(int preverify_ok, X509_STORE_CTX *ctx)
+{
+    VALUE cb, ssl_obj, sslctx_obj, verify_hostname, ret;
+    SSL *ssl;
+    int status;
+
+    ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
+    cb = (VALUE)SSL_get_ex_data(ssl, ossl_ssl_ex_vcb_idx);
+    ssl_obj = (VALUE)SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx);
+    sslctx_obj = rb_attr_get(ssl_obj, id_i_context);
+    verify_hostname = rb_attr_get(sslctx_obj, id_i_verify_hostname);
+
+    if (preverify_ok && RTEST(verify_hostname) && !SSL_is_server(ssl) &&
+	!X509_STORE_CTX_get_error_depth(ctx)) {
+	ret = rb_protect(call_verify_certificate_identity, (VALUE)ctx, &status);
+	if (status) {
+	    rb_ivar_set(ssl_obj, ID_callback_state, INT2NUM(status));
+	    return 0;
+	}
+	preverify_ok = ret == Qtrue;
+    }
+
+    return ossl_verify_cb_call(cb, preverify_ok, ctx);
+}
+
+static VALUE
+ossl_call_session_get_cb(VALUE ary)
+{
+    VALUE ssl_obj, cb;
+
+    Check_Type(ary, T_ARRAY);
+    ssl_obj = rb_ary_entry(ary, 0);
+
+    cb = rb_funcall(ssl_obj, rb_intern("session_get_cb"), 0);
+    if (NIL_P(cb)) return Qnil;
+
+    return rb_funcall(cb, rb_intern("call"), 1, ary);
+}
+
+/* this method is currently only called for servers (in OpenSSL <= 0.9.8e) */
+static SSL_SESSION *
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER)
+ossl_sslctx_session_get_cb(SSL *ssl, const unsigned char *buf, int len, int *copy)
+#else
+ossl_sslctx_session_get_cb(SSL *ssl, unsigned char *buf, int len, int *copy)
+#endif
+{
+    VALUE ary, ssl_obj, ret_obj;
+    SSL_SESSION *sess;
+    void *ptr;
+    int state = 0;
+
+    OSSL_Debug("SSL SESSION get callback entered");
+    if ((ptr = SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx)) == NULL)
+    	return NULL;
+    ssl_obj = (VALUE)ptr;
+    ary = rb_ary_new2(2);
+    rb_ary_push(ary, ssl_obj);
+    rb_ary_push(ary, rb_str_new((const char *)buf, len));
+
+    ret_obj = rb_protect(ossl_call_session_get_cb, ary, &state);
+    if (state) {
+        rb_ivar_set(ssl_obj, ID_callback_state, INT2NUM(state));
+        return NULL;
+    }
+    if (!rb_obj_is_instance_of(ret_obj, cSSLSession))
+        return NULL;
+
+    SafeGetSSLSession(ret_obj, sess);
+    *copy = 1;
+
+    return sess;
+}
+
+static VALUE
+ossl_call_session_new_cb(VALUE ary)
+{
+    VALUE ssl_obj, cb;
+
+    Check_Type(ary, T_ARRAY);
+    ssl_obj = rb_ary_entry(ary, 0);
+
+    cb = rb_funcall(ssl_obj, rb_intern("session_new_cb"), 0);
+    if (NIL_P(cb)) return Qnil;
+
+    return rb_funcall(cb, rb_intern("call"), 1, ary);
+}
+
+/* return 1 normal.  return 0 removes the session */
+static int
+ossl_sslctx_session_new_cb(SSL *ssl, SSL_SESSION *sess)
+{
+    VALUE ary, ssl_obj, sess_obj;
+    void *ptr;
+    int state = 0;
+
+    OSSL_Debug("SSL SESSION new callback entered");
+
+    if ((ptr = SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx)) == NULL)
+    	return 1;
+    ssl_obj = (VALUE)ptr;
+    sess_obj = rb_obj_alloc(cSSLSession);
+    SSL_SESSION_up_ref(sess);
+    DATA_PTR(sess_obj) = sess;
+
+    ary = rb_ary_new2(2);
+    rb_ary_push(ary, ssl_obj);
+    rb_ary_push(ary, sess_obj);
+
+    rb_protect(ossl_call_session_new_cb, ary, &state);
+    if (state) {
+        rb_ivar_set(ssl_obj, ID_callback_state, INT2NUM(state));
+    }
+
+    /*
+     * return 0 which means to OpenSSL that the session is still
+     * valid (since we created Ruby Session object) and was not freed by us
+     * with SSL_SESSION_free(). Call SSLContext#remove_session(sess) in
+     * session_get_cb block if you don't want OpenSSL to cache the session
+     * internally.
+     */
+    return 0;
+}
+
+static VALUE
+ossl_call_session_remove_cb(VALUE ary)
+{
+    VALUE sslctx_obj, cb;
+
+    Check_Type(ary, T_ARRAY);
+    sslctx_obj = rb_ary_entry(ary, 0);
+
+    cb = rb_attr_get(sslctx_obj, id_i_session_remove_cb);
+    if (NIL_P(cb)) return Qnil;
+
+    return rb_funcall(cb, rb_intern("call"), 1, ary);
+}
+
+static void
+ossl_sslctx_session_remove_cb(SSL_CTX *ctx, SSL_SESSION *sess)
+{
+    VALUE ary, sslctx_obj, sess_obj;
+    void *ptr;
+    int state = 0;
+
+    OSSL_Debug("SSL SESSION remove callback entered");
+
+    if ((ptr = SSL_CTX_get_ex_data(ctx, ossl_ssl_ex_ptr_idx)) == NULL)
+    	return;
+    sslctx_obj = (VALUE)ptr;
+    sess_obj = rb_obj_alloc(cSSLSession);
+    SSL_SESSION_up_ref(sess);
+    DATA_PTR(sess_obj) = sess;
+
+    ary = rb_ary_new2(2);
+    rb_ary_push(ary, sslctx_obj);
+    rb_ary_push(ary, sess_obj);
+
+    rb_protect(ossl_call_session_remove_cb, ary, &state);
+    if (state) {
+/*
+  the SSL_CTX is frozen, nowhere to save state.
+  there is no common accessor method to check it either.
+        rb_ivar_set(sslctx_obj, ID_callback_state, INT2NUM(state));
+*/
+    }
+}
+
+static VALUE
+ossl_sslctx_add_extra_chain_cert_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, arg))
+{
+    X509 *x509;
+    SSL_CTX *ctx;
+
+    GetSSLCTX(arg, ctx);
+    x509 = DupX509CertPtr(i);
+    if(!SSL_CTX_add_extra_chain_cert(ctx, x509)){
+	ossl_raise(eSSLError, NULL);
+    }
+
+    return i;
+}
+
+static VALUE ossl_sslctx_setup(VALUE self);
+
+#ifdef HAVE_SSL_SET_TLSEXT_HOST_NAME
+static VALUE
+ossl_call_servername_cb(VALUE ary)
+{
+    VALUE ssl_obj, sslctx_obj, cb, ret_obj;
+
+    Check_Type(ary, T_ARRAY);
+    ssl_obj = rb_ary_entry(ary, 0);
+
+    sslctx_obj = rb_attr_get(ssl_obj, id_i_context);
+    cb = rb_attr_get(sslctx_obj, id_i_servername_cb);
+    if (NIL_P(cb)) return Qnil;
+
+    ret_obj = rb_funcall(cb, rb_intern("call"), 1, ary);
+    if (rb_obj_is_kind_of(ret_obj, cSSLContext)) {
+        SSL *ssl;
+        SSL_CTX *ctx2;
+
+        ossl_sslctx_setup(ret_obj);
+        GetSSL(ssl_obj, ssl);
+        GetSSLCTX(ret_obj, ctx2);
+        SSL_set_SSL_CTX(ssl, ctx2);
+        rb_ivar_set(ssl_obj, id_i_context, ret_obj);
+    } else if (!NIL_P(ret_obj)) {
+	ossl_raise(rb_eArgError, "servername_cb must return an "
+		   "OpenSSL::SSL::SSLContext object or nil");
+    }
+
+    return ret_obj;
+}
+
+static int
+ssl_servername_cb(SSL *ssl, int *ad, void *arg)
+{
+    VALUE ary, ssl_obj;
+    void *ptr;
+    int state = 0;
+    const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
+
+    if (!servername)
+        return SSL_TLSEXT_ERR_OK;
+
+    if ((ptr = SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx)) == NULL)
+    	return SSL_TLSEXT_ERR_ALERT_FATAL;
+    ssl_obj = (VALUE)ptr;
+    ary = rb_ary_new2(2);
+    rb_ary_push(ary, ssl_obj);
+    rb_ary_push(ary, rb_str_new2(servername));
+
+    rb_protect(ossl_call_servername_cb, ary, &state);
+    if (state) {
+        rb_ivar_set(ssl_obj, ID_callback_state, INT2NUM(state));
+        return SSL_TLSEXT_ERR_ALERT_FATAL;
+    }
+
+    return SSL_TLSEXT_ERR_OK;
+}
+#endif
+
+static void
+ssl_renegotiation_cb(const SSL *ssl)
+{
+    VALUE ssl_obj, sslctx_obj, cb;
+    void *ptr;
+
+    if ((ptr = SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx)) == NULL)
+	ossl_raise(eSSLError, "SSL object could not be retrieved");
+    ssl_obj = (VALUE)ptr;
+
+    sslctx_obj = rb_attr_get(ssl_obj, id_i_context);
+    cb = rb_attr_get(sslctx_obj, id_i_renegotiation_cb);
+    if (NIL_P(cb)) return;
+
+    (void) rb_funcall(cb, rb_intern("call"), 1, ssl_obj);
+}
+
+#if defined(HAVE_SSL_CTX_SET_NEXT_PROTO_SELECT_CB) || \
+    defined(HAVE_SSL_CTX_SET_ALPN_SELECT_CB)
+static VALUE
+ssl_npn_encode_protocol_i(VALUE cur, VALUE encoded)
+{
+    int len = RSTRING_LENINT(cur);
+    char len_byte;
+    if (len < 1 || len > 255)
+	ossl_raise(eSSLError, "Advertised protocol must have length 1..255");
+    /* Encode the length byte */
+    len_byte = len;
+    rb_str_buf_cat(encoded, &len_byte, 1);
+    rb_str_buf_cat(encoded, RSTRING_PTR(cur), len);
+    return Qnil;
+}
+
+static VALUE
+ssl_encode_npn_protocols(VALUE protocols)
+{
+    VALUE encoded = rb_str_new(NULL, 0);
+    rb_iterate(rb_each, protocols, ssl_npn_encode_protocol_i, encoded);
+    return encoded;
+}
+
+struct npn_select_cb_common_args {
+    VALUE cb;
+    const unsigned char *in;
+    unsigned inlen;
+};
+
+static VALUE
+npn_select_cb_common_i(VALUE tmp)
+{
+    struct npn_select_cb_common_args *args = (void *)tmp;
+    const unsigned char *in = args->in, *in_end = in + args->inlen;
+    unsigned char l;
+    long len;
+    VALUE selected, protocols = rb_ary_new();
+
+    /* assume OpenSSL verifies this format */
+    /* The format is len_1|proto_1|...|len_n|proto_n */
+    while (in < in_end) {
+	l = *in++;
+	rb_ary_push(protocols, rb_str_new((const char *)in, l));
+	in += l;
+    }
+
+    selected = rb_funcall(args->cb, rb_intern("call"), 1, protocols);
+    StringValue(selected);
+    len = RSTRING_LEN(selected);
+    if (len < 1 || len >= 256) {
+	ossl_raise(eSSLError, "Selected protocol name must have length 1..255");
+    }
+
+    return selected;
+}
+
+static int
+ssl_npn_select_cb_common(SSL *ssl, VALUE cb, const unsigned char **out,
+			 unsigned char *outlen, const unsigned char *in,
+			 unsigned int inlen)
+{
+    VALUE selected;
+    int status;
+    struct npn_select_cb_common_args args;
+
+    args.cb = cb;
+    args.in = in;
+    args.inlen = inlen;
+
+    selected = rb_protect(npn_select_cb_common_i, (VALUE)&args, &status);
+    if (status) {
+	VALUE ssl_obj = (VALUE)SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx);
+
+	rb_ivar_set(ssl_obj, ID_callback_state, INT2NUM(status));
+	return SSL_TLSEXT_ERR_ALERT_FATAL;
+    }
+
+    *out = (unsigned char *)RSTRING_PTR(selected);
+    *outlen = (unsigned char)RSTRING_LEN(selected);
+
+    return SSL_TLSEXT_ERR_OK;
+}
+#endif
+
+#ifdef HAVE_SSL_CTX_SET_NEXT_PROTO_SELECT_CB
+static int
+ssl_npn_advertise_cb(SSL *ssl, const unsigned char **out, unsigned int *outlen,
+		     void *arg)
+{
+    VALUE protocols = (VALUE)arg;
+
+    *out = (const unsigned char *) RSTRING_PTR(protocols);
+    *outlen = RSTRING_LENINT(protocols);
+
+    return SSL_TLSEXT_ERR_OK;
+}
+
+static int
+ssl_npn_select_cb(SSL *ssl, unsigned char **out, unsigned char *outlen,
+		  const unsigned char *in, unsigned int inlen, void *arg)
+{
+    VALUE sslctx_obj, cb;
+
+    sslctx_obj = (VALUE) arg;
+    cb = rb_attr_get(sslctx_obj, id_i_npn_select_cb);
+
+    return ssl_npn_select_cb_common(ssl, cb, (const unsigned char **)out,
+				    outlen, in, inlen);
+}
+#endif
+
+#ifdef HAVE_SSL_CTX_SET_ALPN_SELECT_CB
+static int
+ssl_alpn_select_cb(SSL *ssl, const unsigned char **out, unsigned char *outlen,
+		   const unsigned char *in, unsigned int inlen, void *arg)
+{
+    VALUE sslctx_obj, cb;
+
+    sslctx_obj = (VALUE) arg;
+    cb = rb_attr_get(sslctx_obj, id_i_alpn_select_cb);
+
+    return ssl_npn_select_cb_common(ssl, cb, out, outlen, in, inlen);
+}
+#endif
+
+/* This function may serve as the entry point to support further callbacks. */
+static void
+ssl_info_cb(const SSL *ssl, int where, int val)
+{
+    int is_server = SSL_is_server((SSL *)ssl);
+
+    if (is_server && where & SSL_CB_HANDSHAKE_START) {
+	ssl_renegotiation_cb(ssl);
+    }
+}
+
+/*
+ * Gets various OpenSSL options.
+ */
+static VALUE
+ossl_sslctx_get_options(VALUE self)
+{
+    SSL_CTX *ctx;
+    GetSSLCTX(self, ctx);
+    return LONG2NUM(SSL_CTX_get_options(ctx));
+}
+
+/*
+ * Sets various OpenSSL options.
+ */
+static VALUE
+ossl_sslctx_set_options(VALUE self, VALUE options)
+{
+    SSL_CTX *ctx;
+
+    rb_check_frozen(self);
+    GetSSLCTX(self, ctx);
+
+    SSL_CTX_clear_options(ctx, SSL_CTX_get_options(ctx));
+
+    if (NIL_P(options)) {
+	SSL_CTX_set_options(ctx, SSL_OP_ALL);
+    } else {
+	SSL_CTX_set_options(ctx, NUM2LONG(options));
+    }
+
+    return self;
+}
+
+/*
+ * call-seq:
+ *    ctx.setup => Qtrue # first time
+ *    ctx.setup => nil # thereafter
+ *
+ * This method is called automatically when a new SSLSocket is created.
+ * However, it is not thread-safe and must be called before creating
+ * SSLSocket objects in a multi-threaded program.
+ */
+static VALUE
+ossl_sslctx_setup(VALUE self)
+{
+    SSL_CTX *ctx;
+    X509 *cert = NULL, *client_ca = NULL;
+    EVP_PKEY *key = NULL;
+    char *ca_path = NULL, *ca_file = NULL;
+    int verify_mode;
+    long i;
+    VALUE val;
+
+    if(OBJ_FROZEN(self)) return Qnil;
+    GetSSLCTX(self, ctx);
+
+#if !defined(OPENSSL_NO_DH)
+    SSL_CTX_set_tmp_dh_callback(ctx, ossl_tmp_dh_callback);
+#endif
+
+#if !defined(OPENSSL_NO_EC)
+    /* We added SSLContext#tmp_ecdh_callback= in Ruby 2.3.0,
+     * but SSL_CTX_set_tmp_ecdh_callback() was removed in OpenSSL 1.1.0. */
+    if (RTEST(rb_attr_get(self, id_i_tmp_ecdh_callback))) {
+# if defined(HAVE_SSL_CTX_SET_TMP_ECDH_CALLBACK)
+	rb_warn("#tmp_ecdh_callback= is deprecated; use #ecdh_curves= instead");
+	SSL_CTX_set_tmp_ecdh_callback(ctx, ossl_tmp_ecdh_callback);
+#  if defined(HAVE_SSL_CTX_SET_ECDH_AUTO)
+	/* tmp_ecdh_callback and ecdh_auto conflict; OpenSSL ignores
+	 * tmp_ecdh_callback. So disable ecdh_auto. */
+	if (!SSL_CTX_set_ecdh_auto(ctx, 0))
+	    ossl_raise(eSSLError, "SSL_CTX_set_ecdh_auto");
+#  endif
+# else
+	ossl_raise(eSSLError, "OpenSSL does not support tmp_ecdh_callback; "
+		   "use #ecdh_curves= instead");
+# endif
+    }
+#endif /* OPENSSL_NO_EC */
+
+    val = rb_attr_get(self, id_i_cert_store);
+    if (!NIL_P(val)) {
+	X509_STORE *store = GetX509StorePtr(val); /* NO NEED TO DUP */
+	SSL_CTX_set_cert_store(ctx, store);
+#if !defined(HAVE_X509_STORE_UP_REF)
+	/*
+         * WORKAROUND:
+	 *   X509_STORE can count references, but
+	 *   X509_STORE_free() doesn't care it.
+	 *   So we won't increment it but mark it by ex_data.
+	 */
+        SSL_CTX_set_ex_data(ctx, ossl_ssl_ex_store_p, (void *)1);
+#else /* Fixed in OpenSSL 1.0.2; bff9ce4db38b (master), 5b4b9ce976fc (1.0.2) */
+	X509_STORE_up_ref(store);
+#endif
+    }
+
+    val = rb_attr_get(self, id_i_extra_chain_cert);
+    if(!NIL_P(val)){
+	rb_block_call(val, rb_intern("each"), 0, 0, ossl_sslctx_add_extra_chain_cert_i, self);
+    }
+
+    /* private key may be bundled in certificate file. */
+    val = rb_attr_get(self, id_i_cert);
+    cert = NIL_P(val) ? NULL : GetX509CertPtr(val); /* NO DUP NEEDED */
+    val = rb_attr_get(self, id_i_key);
+    key = NIL_P(val) ? NULL : GetPrivPKeyPtr(val); /* NO DUP NEEDED */
+    if (cert && key) {
+        if (!SSL_CTX_use_certificate(ctx, cert)) {
+            /* Adds a ref => Safe to FREE */
+            ossl_raise(eSSLError, "SSL_CTX_use_certificate");
+        }
+        if (!SSL_CTX_use_PrivateKey(ctx, key)) {
+            /* Adds a ref => Safe to FREE */
+            ossl_raise(eSSLError, "SSL_CTX_use_PrivateKey");
+        }
+        if (!SSL_CTX_check_private_key(ctx)) {
+            ossl_raise(eSSLError, "SSL_CTX_check_private_key");
+        }
+    }
+
+    val = rb_attr_get(self, id_i_client_ca);
+    if(!NIL_P(val)){
+	if (RB_TYPE_P(val, T_ARRAY)) {
+	    for(i = 0; i < RARRAY_LEN(val); i++){
+		client_ca = GetX509CertPtr(RARRAY_AREF(val, i));
+        	if (!SSL_CTX_add_client_CA(ctx, client_ca)){
+		    /* Copies X509_NAME => FREE it. */
+        	    ossl_raise(eSSLError, "SSL_CTX_add_client_CA");
+        	}
+	    }
+        }
+	else{
+	    client_ca = GetX509CertPtr(val); /* NO DUP NEEDED. */
+            if (!SSL_CTX_add_client_CA(ctx, client_ca)){
+		/* Copies X509_NAME => FREE it. */
+        	ossl_raise(eSSLError, "SSL_CTX_add_client_CA");
+            }
+	}
+    }
+
+    val = rb_attr_get(self, id_i_ca_file);
+    ca_file = NIL_P(val) ? NULL : StringValueCStr(val);
+    val = rb_attr_get(self, id_i_ca_path);
+    ca_path = NIL_P(val) ? NULL : StringValueCStr(val);
+    if(ca_file || ca_path){
+	if (!SSL_CTX_load_verify_locations(ctx, ca_file, ca_path))
+	    rb_warning("can't set verify locations");
+    }
+
+    val = rb_attr_get(self, id_i_verify_mode);
+    verify_mode = NIL_P(val) ? SSL_VERIFY_NONE : NUM2INT(val);
+    SSL_CTX_set_verify(ctx, verify_mode, ossl_ssl_verify_callback);
+    if (RTEST(rb_attr_get(self, id_i_client_cert_cb)))
+	SSL_CTX_set_client_cert_cb(ctx, ossl_client_cert_cb);
+
+    val = rb_attr_get(self, id_i_timeout);
+    if(!NIL_P(val)) SSL_CTX_set_timeout(ctx, NUM2LONG(val));
+
+    val = rb_attr_get(self, id_i_verify_depth);
+    if(!NIL_P(val)) SSL_CTX_set_verify_depth(ctx, NUM2INT(val));
+
+#ifdef HAVE_SSL_CTX_SET_NEXT_PROTO_SELECT_CB
+    val = rb_attr_get(self, id_i_npn_protocols);
+    if (!NIL_P(val)) {
+	VALUE encoded = ssl_encode_npn_protocols(val);
+	rb_ivar_set(self, id_npn_protocols_encoded, encoded);
+	SSL_CTX_set_next_protos_advertised_cb(ctx, ssl_npn_advertise_cb, (void *)encoded);
+	OSSL_Debug("SSL NPN advertise callback added");
+    }
+    if (RTEST(rb_attr_get(self, id_i_npn_select_cb))) {
+	SSL_CTX_set_next_proto_select_cb(ctx, ssl_npn_select_cb, (void *) self);
+	OSSL_Debug("SSL NPN select callback added");
+    }
+#endif
+
+#ifdef HAVE_SSL_CTX_SET_ALPN_SELECT_CB
+    val = rb_attr_get(self, id_i_alpn_protocols);
+    if (!NIL_P(val)) {
+	VALUE rprotos = ssl_encode_npn_protocols(val);
+
+	/* returns 0 on success */
+	if (SSL_CTX_set_alpn_protos(ctx, (unsigned char *)RSTRING_PTR(rprotos),
+				    RSTRING_LENINT(rprotos)))
+	    ossl_raise(eSSLError, "SSL_CTX_set_alpn_protos");
+	OSSL_Debug("SSL ALPN values added");
+    }
+    if (RTEST(rb_attr_get(self, id_i_alpn_select_cb))) {
+	SSL_CTX_set_alpn_select_cb(ctx, ssl_alpn_select_cb, (void *) self);
+	OSSL_Debug("SSL ALPN select callback added");
+    }
+#endif
+
+    rb_obj_freeze(self);
+
+    val = rb_attr_get(self, id_i_session_id_context);
+    if (!NIL_P(val)){
+	StringValue(val);
+	if (!SSL_CTX_set_session_id_context(ctx, (unsigned char *)RSTRING_PTR(val),
+					    RSTRING_LENINT(val))){
+	    ossl_raise(eSSLError, "SSL_CTX_set_session_id_context");
+	}
+    }
+
+    if (RTEST(rb_attr_get(self, id_i_session_get_cb))) {
+	SSL_CTX_sess_set_get_cb(ctx, ossl_sslctx_session_get_cb);
+	OSSL_Debug("SSL SESSION get callback added");
+    }
+    if (RTEST(rb_attr_get(self, id_i_session_new_cb))) {
+	SSL_CTX_sess_set_new_cb(ctx, ossl_sslctx_session_new_cb);
+	OSSL_Debug("SSL SESSION new callback added");
+    }
+    if (RTEST(rb_attr_get(self, id_i_session_remove_cb))) {
+	SSL_CTX_sess_set_remove_cb(ctx, ossl_sslctx_session_remove_cb);
+	OSSL_Debug("SSL SESSION remove callback added");
+    }
+
+#ifdef HAVE_SSL_SET_TLSEXT_HOST_NAME
+    val = rb_attr_get(self, id_i_servername_cb);
+    if (!NIL_P(val)) {
+        SSL_CTX_set_tlsext_servername_callback(ctx, ssl_servername_cb);
+	OSSL_Debug("SSL TLSEXT servername callback added");
+    }
+#endif
+
+    return Qtrue;
+}
+
+static VALUE
+ossl_ssl_cipher_to_ary(const SSL_CIPHER *cipher)
+{
+    VALUE ary;
+    int bits, alg_bits;
+
+    ary = rb_ary_new2(4);
+    rb_ary_push(ary, rb_str_new2(SSL_CIPHER_get_name(cipher)));
+    rb_ary_push(ary, rb_str_new2(SSL_CIPHER_get_version(cipher)));
+    bits = SSL_CIPHER_get_bits(cipher, &alg_bits);
+    rb_ary_push(ary, INT2NUM(bits));
+    rb_ary_push(ary, INT2NUM(alg_bits));
+
+    return ary;
+}
+
+/*
+ * call-seq:
+ *    ctx.ciphers => [[name, version, bits, alg_bits], ...]
+ *
+ * The list of cipher suites configured for this context.
+ */
+static VALUE
+ossl_sslctx_get_ciphers(VALUE self)
+{
+    SSL_CTX *ctx;
+    STACK_OF(SSL_CIPHER) *ciphers;
+    const SSL_CIPHER *cipher;
+    VALUE ary;
+    int i, num;
+
+    GetSSLCTX(self, ctx);
+    if(!ctx){
+        rb_warning("SSL_CTX is not initialized.");
+        return Qnil;
+    }
+    ciphers = SSL_CTX_get_ciphers(ctx);
+
+    if (!ciphers)
+        return rb_ary_new();
+
+    num = sk_SSL_CIPHER_num(ciphers);
+    ary = rb_ary_new2(num);
+    for(i = 0; i < num; i++){
+        cipher = sk_SSL_CIPHER_value(ciphers, i);
+        rb_ary_push(ary, ossl_ssl_cipher_to_ary(cipher));
+    }
+    return ary;
+}
+
+/*
+ * call-seq:
+ *    ctx.ciphers = "cipher1:cipher2:..."
+ *    ctx.ciphers = [name, ...]
+ *    ctx.ciphers = [[name, version, bits, alg_bits], ...]
+ *
+ * Sets the list of available cipher suites for this context.  Note in a server
+ * context some ciphers require the appropriate certificates.  For example, an
+ * RSA cipher suite can only be chosen when an RSA certificate is available.
+ */
+static VALUE
+ossl_sslctx_set_ciphers(VALUE self, VALUE v)
+{
+    SSL_CTX *ctx;
+    VALUE str, elem;
+    int i;
+
+    rb_check_frozen(self);
+    if (NIL_P(v))
+	return v;
+    else if (RB_TYPE_P(v, T_ARRAY)) {
+        str = rb_str_new(0, 0);
+        for (i = 0; i < RARRAY_LEN(v); i++) {
+            elem = rb_ary_entry(v, i);
+            if (RB_TYPE_P(elem, T_ARRAY)) elem = rb_ary_entry(elem, 0);
+            elem = rb_String(elem);
+            rb_str_append(str, elem);
+            if (i < RARRAY_LEN(v)-1) rb_str_cat2(str, ":");
+        }
+    } else {
+        str = v;
+        StringValue(str);
+    }
+
+    GetSSLCTX(self, ctx);
+    if(!ctx){
+        ossl_raise(eSSLError, "SSL_CTX is not initialized.");
+        return Qnil;
+    }
+    if (!SSL_CTX_set_cipher_list(ctx, StringValueCStr(str))) {
+        ossl_raise(eSSLError, "SSL_CTX_set_cipher_list");
+    }
+
+    return v;
+}
+
+#if !defined(OPENSSL_NO_EC)
+/*
+ * call-seq:
+ *    ctx.ecdh_curves = curve_list -> curve_list
+ *
+ * Sets the list of "supported elliptic curves" for this context.
+ *
+ * For a TLS client, the list is directly used in the Supported Elliptic Curves
+ * Extension. For a server, the list is used by OpenSSL to determine the set of
+ * shared curves. OpenSSL will pick the most appropriate one from it.
+ *
+ * Note that this works differently with old OpenSSL (<= 1.0.1). Only one curve
+ * can be set, and this has no effect for TLS clients.
+ *
+ * === Example
+ *   ctx1 = OpenSSL::SSL::SSLContext.new
+ *   ctx1.ecdh_curves = "X25519:P-256:P-224"
+ *   svr = OpenSSL::SSL::SSLServer.new(tcp_svr, ctx1)
+ *   Thread.new { svr.accept }
+ *
+ *   ctx2 = OpenSSL::SSL::SSLContext.new
+ *   ctx2.ecdh_curves = "P-256"
+ *   cli = OpenSSL::SSL::SSLSocket.new(tcp_sock, ctx2)
+ *   cli.connect
+ *
+ *   p cli.tmp_key.group.curve_name
+ *   # => "prime256v1" (is an alias for NIST P-256)
+ */
+static VALUE
+ossl_sslctx_set_ecdh_curves(VALUE self, VALUE arg)
+{
+    SSL_CTX *ctx;
+
+    rb_check_frozen(self);
+    GetSSLCTX(self, ctx);
+    StringValueCStr(arg);
+
+#if defined(HAVE_SSL_CTX_SET1_CURVES_LIST)
+    if (!SSL_CTX_set1_curves_list(ctx, RSTRING_PTR(arg)))
+	ossl_raise(eSSLError, NULL);
+#else
+    /* OpenSSL does not have SSL_CTX_set1_curves_list()... Fallback to
+     * SSL_CTX_set_tmp_ecdh(). So only the first curve is used. */
+    {
+	VALUE curve, splitted;
+	EC_KEY *ec;
+	int nid;
+
+	splitted = rb_str_split(arg, ":");
+	if (!RARRAY_LEN(splitted))
+	    ossl_raise(eSSLError, "invalid input format");
+	curve = RARRAY_AREF(splitted, 0);
+	StringValueCStr(curve);
+
+	/* SSL_CTX_set1_curves_list() accepts NIST names */
+	nid = EC_curve_nist2nid(RSTRING_PTR(curve));
+	if (nid == NID_undef)
+	    nid = OBJ_txt2nid(RSTRING_PTR(curve));
+	if (nid == NID_undef)
+	    ossl_raise(eSSLError, "unknown curve name");
+
+	ec = EC_KEY_new_by_curve_name(nid);
+	if (!ec)
+	    ossl_raise(eSSLError, NULL);
+	EC_KEY_set_asn1_flag(ec, OPENSSL_EC_NAMED_CURVE);
+	if (!SSL_CTX_set_tmp_ecdh(ctx, ec)) {
+	    EC_KEY_free(ec);
+	    ossl_raise(eSSLError, "SSL_CTX_set_tmp_ecdh");
+	}
+	EC_KEY_free(ec);
+# if defined(HAVE_SSL_CTX_SET_ECDH_AUTO)
+	/* tmp_ecdh and ecdh_auto conflict. tmp_ecdh is ignored when ecdh_auto
+	 * is enabled. So disable ecdh_auto. */
+	if (!SSL_CTX_set_ecdh_auto(ctx, 0))
+	    ossl_raise(eSSLError, "SSL_CTX_set_ecdh_auto");
+# endif
+    }
+#endif
+
+    return arg;
+}
+#else
+#define ossl_sslctx_set_ecdh_curves rb_f_notimplement
+#endif
+
+/*
+ * call-seq:
+ *    ctx.security_level -> Integer
+ *
+ * Returns the security level for the context.
+ *
+ * See also OpenSSL::SSL::SSLContext#security_level=.
+ */
+static VALUE
+ossl_sslctx_get_security_level(VALUE self)
+{
+    SSL_CTX *ctx;
+
+    GetSSLCTX(self, ctx);
+
+#if defined(HAVE_SSL_CTX_GET_SECURITY_LEVEL)
+    return INT2NUM(SSL_CTX_get_security_level(ctx));
+#else
+    (void)ctx;
+    return INT2FIX(0);
+#endif
+}
+
+/*
+ * call-seq:
+ *    ctx.security_level = integer
+ *
+ * Sets the security level for the context. OpenSSL limits parameters according
+ * to the level. The "parameters" include: ciphersuites, curves, key sizes,
+ * certificate signature algorithms, protocol version and so on. For example,
+ * level 1 rejects parameters offering below 80 bits of security, such as
+ * ciphersuites using MD5 for the MAC or RSA keys shorter than 1024 bits.
+ *
+ * Note that attempts to set such parameters with insufficient security are
+ * also blocked. You need to lower the level first.
+ *
+ * This feature is not supported in OpenSSL < 1.1.0, and setting the level to
+ * other than 0 will raise NotImplementedError. Level 0 means everything is
+ * permitted, the same behavior as previous versions of OpenSSL.
+ *
+ * See the manpage of SSL_CTX_set_security_level(3) for details.
+ */
+static VALUE
+ossl_sslctx_set_security_level(VALUE self, VALUE value)
+{
+    SSL_CTX *ctx;
+
+    rb_check_frozen(self);
+    GetSSLCTX(self, ctx);
+
+#if defined(HAVE_SSL_CTX_GET_SECURITY_LEVEL)
+    SSL_CTX_set_security_level(ctx, NUM2INT(value));
+#else
+    (void)ctx;
+    if (NUM2INT(value) != 0)
+	ossl_raise(rb_eNotImpError, "setting security level to other than 0 is "
+		   "not supported in this version of OpenSSL");
+#endif
+
+    return value;
+}
+
+/*
+ *  call-seq:
+ *     ctx.session_add(session) -> true | false
+ *
+ * Adds +session+ to the session cache.
+ */
+static VALUE
+ossl_sslctx_session_add(VALUE self, VALUE arg)
+{
+    SSL_CTX *ctx;
+    SSL_SESSION *sess;
+
+    GetSSLCTX(self, ctx);
+    SafeGetSSLSession(arg, sess);
+
+    return SSL_CTX_add_session(ctx, sess) == 1 ? Qtrue : Qfalse;
+}
+
+/*
+ *  call-seq:
+ *     ctx.session_remove(session) -> true | false
+ *
+ * Removes +session+ from the session cache.
+ */
+static VALUE
+ossl_sslctx_session_remove(VALUE self, VALUE arg)
+{
+    SSL_CTX *ctx;
+    SSL_SESSION *sess;
+
+    GetSSLCTX(self, ctx);
+    SafeGetSSLSession(arg, sess);
+
+    return SSL_CTX_remove_session(ctx, sess) == 1 ? Qtrue : Qfalse;
+}
+
+/*
+ *  call-seq:
+ *     ctx.session_cache_mode -> Integer
+ *
+ * The current session cache mode.
+ */
+static VALUE
+ossl_sslctx_get_session_cache_mode(VALUE self)
+{
+    SSL_CTX *ctx;
+
+    GetSSLCTX(self, ctx);
+
+    return LONG2NUM(SSL_CTX_get_session_cache_mode(ctx));
+}
+
+/*
+ *  call-seq:
+ *     ctx.session_cache_mode=(integer) -> Integer
+ *
+ * Sets the SSL session cache mode.  Bitwise-or together the desired
+ * SESSION_CACHE_* constants to set.  See SSL_CTX_set_session_cache_mode(3) for
+ * details.
+ */
+static VALUE
+ossl_sslctx_set_session_cache_mode(VALUE self, VALUE arg)
+{
+    SSL_CTX *ctx;
+
+    GetSSLCTX(self, ctx);
+
+    SSL_CTX_set_session_cache_mode(ctx, NUM2LONG(arg));
+
+    return arg;
+}
+
+/*
+ *  call-seq:
+ *     ctx.session_cache_size -> Integer
+ *
+ * Returns the current session cache size.  Zero is used to represent an
+ * unlimited cache size.
+ */
+static VALUE
+ossl_sslctx_get_session_cache_size(VALUE self)
+{
+    SSL_CTX *ctx;
+
+    GetSSLCTX(self, ctx);
+
+    return LONG2NUM(SSL_CTX_sess_get_cache_size(ctx));
+}
+
+/*
+ *  call-seq:
+ *     ctx.session_cache_size=(integer) -> Integer
+ *
+ * Sets the session cache size.  Returns the previously valid session cache
+ * size.  Zero is used to represent an unlimited session cache size.
+ */
+static VALUE
+ossl_sslctx_set_session_cache_size(VALUE self, VALUE arg)
+{
+    SSL_CTX *ctx;
+
+    GetSSLCTX(self, ctx);
+
+    SSL_CTX_sess_set_cache_size(ctx, NUM2LONG(arg));
+
+    return arg;
+}
+
+/*
+ *  call-seq:
+ *     ctx.session_cache_stats -> Hash
+ *
+ * Returns a Hash containing the following keys:
+ *
+ * :accept:: Number of started SSL/TLS handshakes in server mode
+ * :accept_good:: Number of established SSL/TLS sessions in server mode
+ * :accept_renegotiate:: Number of start renegotiations in server mode
+ * :cache_full:: Number of sessions that were removed due to cache overflow
+ * :cache_hits:: Number of successfully reused connections
+ * :cache_misses:: Number of sessions proposed by clients that were not found
+ *                 in the cache
+ * :cache_num:: Number of sessions in the internal session cache
+ * :cb_hits:: Number of sessions retrieved from the external cache in server
+ *            mode
+ * :connect:: Number of started SSL/TLS handshakes in client mode
+ * :connect_good:: Number of established SSL/TLS sessions in client mode
+ * :connect_renegotiate:: Number of start renegotiations in client mode
+ * :timeouts:: Number of sessions proposed by clients that were found in the
+ *             cache but had expired due to timeouts
+ */
+static VALUE
+ossl_sslctx_get_session_cache_stats(VALUE self)
+{
+    SSL_CTX *ctx;
+    VALUE hash;
+
+    GetSSLCTX(self, ctx);
+
+    hash = rb_hash_new();
+    rb_hash_aset(hash, ID2SYM(rb_intern("cache_num")), LONG2NUM(SSL_CTX_sess_number(ctx)));
+    rb_hash_aset(hash, ID2SYM(rb_intern("connect")), LONG2NUM(SSL_CTX_sess_connect(ctx)));
+    rb_hash_aset(hash, ID2SYM(rb_intern("connect_good")), LONG2NUM(SSL_CTX_sess_connect_good(ctx)));
+    rb_hash_aset(hash, ID2SYM(rb_intern("connect_renegotiate")), LONG2NUM(SSL_CTX_sess_connect_renegotiate(ctx)));
+    rb_hash_aset(hash, ID2SYM(rb_intern("accept")), LONG2NUM(SSL_CTX_sess_accept(ctx)));
+    rb_hash_aset(hash, ID2SYM(rb_intern("accept_good")), LONG2NUM(SSL_CTX_sess_accept_good(ctx)));
+    rb_hash_aset(hash, ID2SYM(rb_intern("accept_renegotiate")), LONG2NUM(SSL_CTX_sess_accept_renegotiate(ctx)));
+    rb_hash_aset(hash, ID2SYM(rb_intern("cache_hits")), LONG2NUM(SSL_CTX_sess_hits(ctx)));
+    rb_hash_aset(hash, ID2SYM(rb_intern("cb_hits")), LONG2NUM(SSL_CTX_sess_cb_hits(ctx)));
+    rb_hash_aset(hash, ID2SYM(rb_intern("cache_misses")), LONG2NUM(SSL_CTX_sess_misses(ctx)));
+    rb_hash_aset(hash, ID2SYM(rb_intern("cache_full")), LONG2NUM(SSL_CTX_sess_cache_full(ctx)));
+    rb_hash_aset(hash, ID2SYM(rb_intern("timeouts")), LONG2NUM(SSL_CTX_sess_timeouts(ctx)));
+
+    return hash;
+}
+
+
+/*
+ *  call-seq:
+ *     ctx.flush_sessions(time | nil) -> self
+ *
+ * Removes sessions in the internal cache that have expired at +time+.
+ */
+static VALUE
+ossl_sslctx_flush_sessions(int argc, VALUE *argv, VALUE self)
+{
+    VALUE arg1;
+    SSL_CTX *ctx;
+    time_t tm = 0;
+
+    rb_scan_args(argc, argv, "01", &arg1);
+
+    GetSSLCTX(self, ctx);
+
+    if (NIL_P(arg1)) {
+        tm = time(0);
+    } else if (rb_obj_is_instance_of(arg1, rb_cTime)) {
+        tm = NUM2LONG(rb_funcall(arg1, rb_intern("to_i"), 0));
+    } else {
+        ossl_raise(rb_eArgError, "arg must be Time or nil");
+    }
+
+    SSL_CTX_flush_sessions(ctx, (long)tm);
+
+    return self;
+}
+
+/*
+ * SSLSocket class
+ */
+#ifndef OPENSSL_NO_SOCK
+static inline int
+ssl_started(SSL *ssl)
+{
+    /* the FD is set in ossl_ssl_setup(), called by #connect or #accept */
+    return SSL_get_fd(ssl) >= 0;
+}
+
+static void
+ossl_ssl_free(void *ssl)
+{
+    SSL_free(ssl);
+}
+
+const rb_data_type_t ossl_ssl_type = {
+    "OpenSSL/SSL",
+    {
+	0, ossl_ssl_free,
+    },
+    0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
+};
+
+static VALUE
+ossl_ssl_s_alloc(VALUE klass)
+{
+    return TypedData_Wrap_Struct(klass, &ossl_ssl_type, NULL);
+}
+
+/*
+ * call-seq:
+ *    SSLSocket.new(io) => aSSLSocket
+ *    SSLSocket.new(io, ctx) => aSSLSocket
+ *
+ * Creates a new SSL socket from +io+ which must be a real IO object (not an
+ * IO-like object that responds to read/write).
+ *
+ * If +ctx+ is provided the SSL Sockets initial params will be taken from
+ * the context.
+ *
+ * The OpenSSL::Buffering module provides additional IO methods.
+ *
+ * This method will freeze the SSLContext if one is provided;
+ * however, session management is still allowed in the frozen SSLContext.
+ */
+static VALUE
+ossl_ssl_initialize(int argc, VALUE *argv, VALUE self)
+{
+    VALUE io, v_ctx, verify_cb;
+    SSL *ssl;
+    SSL_CTX *ctx;
+
+    TypedData_Get_Struct(self, SSL, &ossl_ssl_type, ssl);
+    if (ssl)
+	ossl_raise(eSSLError, "SSL already initialized");
+
+    if (rb_scan_args(argc, argv, "11", &io, &v_ctx) == 1)
+	v_ctx = rb_funcall(cSSLContext, rb_intern("new"), 0);
+
+    GetSSLCTX(v_ctx, ctx);
+    rb_ivar_set(self, id_i_context, v_ctx);
+    ossl_sslctx_setup(v_ctx);
+
+    if (rb_respond_to(io, rb_intern("nonblock=")))
+	rb_funcall(io, rb_intern("nonblock="), 1, Qtrue);
+    rb_ivar_set(self, id_i_io, io);
+
+    ssl = SSL_new(ctx);
+    if (!ssl)
+	ossl_raise(eSSLError, NULL);
+    RTYPEDDATA_DATA(self) = ssl;
+
+    SSL_set_ex_data(ssl, ossl_ssl_ex_ptr_idx, (void *)self);
+    SSL_set_info_callback(ssl, ssl_info_cb);
+    verify_cb = rb_attr_get(v_ctx, id_i_verify_callback);
+    SSL_set_ex_data(ssl, ossl_ssl_ex_vcb_idx, (void *)verify_cb);
+
+    rb_call_super(0, NULL);
+
+    return self;
+}
+
+static VALUE
+ossl_ssl_setup(VALUE self)
+{
+    VALUE io;
+    SSL *ssl;
+    rb_io_t *fptr;
+
+    GetSSL(self, ssl);
+    if (ssl_started(ssl))
+	return Qtrue;
+
+    io = rb_attr_get(self, id_i_io);
+    GetOpenFile(io, fptr);
+    rb_io_check_readable(fptr);
+    rb_io_check_writable(fptr);
+    if (!SSL_set_fd(ssl, TO_SOCKET(FPTR_TO_FD(fptr))))
+	ossl_raise(eSSLError, "SSL_set_fd");
+
+    return Qtrue;
+}
+
+#ifdef _WIN32
+#define ssl_get_error(ssl, ret) (errno = rb_w32_map_errno(WSAGetLastError()), SSL_get_error((ssl), (ret)))
+#else
+#define ssl_get_error(ssl, ret) SSL_get_error((ssl), (ret))
+#endif
+
+static void
+write_would_block(int nonblock)
+{
+    if (nonblock)
+	ossl_raise(eSSLErrorWaitWritable, "write would block");
+}
+
+static void
+read_would_block(int nonblock)
+{
+    if (nonblock)
+	ossl_raise(eSSLErrorWaitReadable, "read would block");
+}
+
+static int
+no_exception_p(VALUE opts)
+{
+    if (RB_TYPE_P(opts, T_HASH) &&
+          rb_hash_lookup2(opts, sym_exception, Qundef) == Qfalse)
+	return 1;
+    return 0;
+}
+
+static VALUE
+ossl_start_ssl(VALUE self, int (*func)(), const char *funcname, VALUE opts)
+{
+    SSL *ssl;
+    rb_io_t *fptr;
+    int ret, ret2;
+    VALUE cb_state;
+    int nonblock = opts != Qfalse;
+
+    rb_ivar_set(self, ID_callback_state, Qnil);
+
+    GetSSL(self, ssl);
+
+    GetOpenFile(rb_attr_get(self, id_i_io), fptr);
+    for(;;){
+	ret = func(ssl);
+
+	cb_state = rb_attr_get(self, ID_callback_state);
+        if (!NIL_P(cb_state)) {
+	    /* must cleanup OpenSSL error stack before re-raising */
+	    ossl_clear_error();
+	    rb_jump_tag(NUM2INT(cb_state));
+	}
+
+	if (ret > 0)
+	    break;
+
+	switch((ret2 = ssl_get_error(ssl, ret))){
+	case SSL_ERROR_WANT_WRITE:
+            if (no_exception_p(opts)) { return sym_wait_writable; }
+            write_would_block(nonblock);
+            rb_io_wait_writable(FPTR_TO_FD(fptr));
+            continue;
+	case SSL_ERROR_WANT_READ:
+            if (no_exception_p(opts)) { return sym_wait_readable; }
+            read_would_block(nonblock);
+            rb_io_wait_readable(FPTR_TO_FD(fptr));
+            continue;
+	case SSL_ERROR_SYSCALL:
+	    if (errno) rb_sys_fail(funcname);
+	    ossl_raise(eSSLError, "%s SYSCALL returned=%d errno=%d state=%s", funcname, ret2, errno, SSL_state_string_long(ssl));
+	default:
+	    ossl_raise(eSSLError, "%s returned=%d errno=%d state=%s", funcname, ret2, errno, SSL_state_string_long(ssl));
+	}
+    }
+
+    return self;
+}
+
+/*
+ * call-seq:
+ *    ssl.connect => self
+ *
+ * Initiates an SSL/TLS handshake with a server.  The handshake may be started
+ * after unencrypted data has been sent over the socket.
+ */
+static VALUE
+ossl_ssl_connect(VALUE self)
+{
+    ossl_ssl_setup(self);
+
+    return ossl_start_ssl(self, SSL_connect, "SSL_connect", Qfalse);
+}
+
+/*
+ * call-seq:
+ *    ssl.connect_nonblock([options]) => self
+ *
+ * Initiates the SSL/TLS handshake as a client in non-blocking manner.
+ *
+ *   # emulates blocking connect
+ *   begin
+ *     ssl.connect_nonblock
+ *   rescue IO::WaitReadable
+ *     IO.select([s2])
+ *     retry
+ *   rescue IO::WaitWritable
+ *     IO.select(nil, [s2])
+ *     retry
+ *   end
+ *
+ * By specifying `exception: false`, the options hash allows you to indicate
+ * that connect_nonblock should not raise an IO::WaitReadable or
+ * IO::WaitWritable exception, but return the symbol :wait_readable or
+ * :wait_writable instead.
+ */
+static VALUE
+ossl_ssl_connect_nonblock(int argc, VALUE *argv, VALUE self)
+{
+    VALUE opts;
+    rb_scan_args(argc, argv, "0:", &opts);
+
+    ossl_ssl_setup(self);
+
+    return ossl_start_ssl(self, SSL_connect, "SSL_connect", opts);
+}
+
+/*
+ * call-seq:
+ *    ssl.accept => self
+ *
+ * Waits for a SSL/TLS client to initiate a handshake.  The handshake may be
+ * started after unencrypted data has been sent over the socket.
+ */
+static VALUE
+ossl_ssl_accept(VALUE self)
+{
+    ossl_ssl_setup(self);
+
+    return ossl_start_ssl(self, SSL_accept, "SSL_accept", Qfalse);
+}
+
+/*
+ * call-seq:
+ *    ssl.accept_nonblock([options]) => self
+ *
+ * Initiates the SSL/TLS handshake as a server in non-blocking manner.
+ *
+ *   # emulates blocking accept
+ *   begin
+ *     ssl.accept_nonblock
+ *   rescue IO::WaitReadable
+ *     IO.select([s2])
+ *     retry
+ *   rescue IO::WaitWritable
+ *     IO.select(nil, [s2])
+ *     retry
+ *   end
+ *
+ * By specifying `exception: false`, the options hash allows you to indicate
+ * that accept_nonblock should not raise an IO::WaitReadable or
+ * IO::WaitWritable exception, but return the symbol :wait_readable or
+ * :wait_writable instead.
+ */
+static VALUE
+ossl_ssl_accept_nonblock(int argc, VALUE *argv, VALUE self)
+{
+    VALUE opts;
+
+    rb_scan_args(argc, argv, "0:", &opts);
+    ossl_ssl_setup(self);
+
+    return ossl_start_ssl(self, SSL_accept, "SSL_accept", opts);
+}
+
+static VALUE
+ossl_ssl_read_internal(int argc, VALUE *argv, VALUE self, int nonblock)
+{
+    SSL *ssl;
+    int ilen, nread = 0;
+    VALUE len, str;
+    rb_io_t *fptr;
+    VALUE io, opts = Qnil;
+
+    if (nonblock) {
+	rb_scan_args(argc, argv, "11:", &len, &str, &opts);
+    } else {
+	rb_scan_args(argc, argv, "11", &len, &str);
+    }
+
+    ilen = NUM2INT(len);
+    if(NIL_P(str)) str = rb_str_new(0, ilen);
+    else{
+        StringValue(str);
+        rb_str_modify(str);
+        rb_str_resize(str, ilen);
+    }
+    if(ilen == 0) return str;
+
+    GetSSL(self, ssl);
+    io = rb_attr_get(self, id_i_io);
+    GetOpenFile(io, fptr);
+    if (ssl_started(ssl)) {
+	if(!nonblock && SSL_pending(ssl) <= 0)
+	    rb_thread_wait_fd(FPTR_TO_FD(fptr));
+	for (;;){
+	    nread = SSL_read(ssl, RSTRING_PTR(str), RSTRING_LENINT(str));
+	    switch(ssl_get_error(ssl, nread)){
+	    case SSL_ERROR_NONE:
+		goto end;
+	    case SSL_ERROR_ZERO_RETURN:
+		if (no_exception_p(opts)) { return Qnil; }
+		rb_eof_error();
+	    case SSL_ERROR_WANT_WRITE:
+		if (no_exception_p(opts)) { return sym_wait_writable; }
+                write_would_block(nonblock);
+                rb_io_wait_writable(FPTR_TO_FD(fptr));
+                continue;
+	    case SSL_ERROR_WANT_READ:
+		if (no_exception_p(opts)) { return sym_wait_readable; }
+                read_would_block(nonblock);
+                rb_io_wait_readable(FPTR_TO_FD(fptr));
+		continue;
+	    case SSL_ERROR_SYSCALL:
+		if (!ERR_peek_error()) {
+		    if (errno)
+			rb_sys_fail(0);
+		    else {
+			/*
+			 * The underlying BIO returned 0. This is actually a
+			 * protocol error. But unfortunately, not all
+			 * implementations cleanly shutdown the TLS connection
+			 * but just shutdown/close the TCP connection. So report
+			 * EOF for now...
+			 */
+			if (no_exception_p(opts)) { return Qnil; }
+			rb_eof_error();
+		    }
+		}
+	    default:
+		ossl_raise(eSSLError, "SSL_read");
+	    }
+        }
+    }
+    else {
+	ID meth = nonblock ? rb_intern("read_nonblock") : rb_intern("sysread");
+
+	rb_warning("SSL session is not started yet.");
+	if (nonblock)
+	    return rb_funcall(io, meth, 3, len, str, opts);
+	else
+	    return rb_funcall(io, meth, 2, len, str);
+    }
+
+  end:
+    rb_str_set_len(str, nread);
+    OBJ_TAINT(str);
+
+    return str;
+}
+
+/*
+ * call-seq:
+ *    ssl.sysread(length) => string
+ *    ssl.sysread(length, buffer) => buffer
+ *
+ * Reads +length+ bytes from the SSL connection.  If a pre-allocated +buffer+
+ * is provided the data will be written into it.
+ */
+static VALUE
+ossl_ssl_read(int argc, VALUE *argv, VALUE self)
+{
+    return ossl_ssl_read_internal(argc, argv, self, 0);
+}
+
+/*
+ * call-seq:
+ *    ssl.sysread_nonblock(length) => string
+ *    ssl.sysread_nonblock(length, buffer) => buffer
+ *    ssl.sysread_nonblock(length[, buffer [, opts]) => buffer
+ *
+ * A non-blocking version of #sysread.  Raises an SSLError if reading would
+ * block.  If "exception: false" is passed, this method returns a symbol of
+ * :wait_readable, :wait_writable, or nil, rather than raising an exception.
+ *
+ * Reads +length+ bytes from the SSL connection.  If a pre-allocated +buffer+
+ * is provided the data will be written into it.
+ */
+static VALUE
+ossl_ssl_read_nonblock(int argc, VALUE *argv, VALUE self)
+{
+    return ossl_ssl_read_internal(argc, argv, self, 1);
+}
+
+static VALUE
+ossl_ssl_write_internal(VALUE self, VALUE str, VALUE opts)
+{
+    SSL *ssl;
+    int nwrite = 0;
+    rb_io_t *fptr;
+    int nonblock = opts != Qfalse;
+    VALUE io;
+
+    StringValue(str);
+    GetSSL(self, ssl);
+    io = rb_attr_get(self, id_i_io);
+    GetOpenFile(io, fptr);
+    if (ssl_started(ssl)) {
+	for (;;){
+	    int num = RSTRING_LENINT(str);
+
+	    /* SSL_write(3ssl) manpage states num == 0 is undefined */
+	    if (num == 0)
+		goto end;
+
+	    nwrite = SSL_write(ssl, RSTRING_PTR(str), num);
+	    switch(ssl_get_error(ssl, nwrite)){
+	    case SSL_ERROR_NONE:
+		goto end;
+	    case SSL_ERROR_WANT_WRITE:
+		if (no_exception_p(opts)) { return sym_wait_writable; }
+                write_would_block(nonblock);
+                rb_io_wait_writable(FPTR_TO_FD(fptr));
+                continue;
+	    case SSL_ERROR_WANT_READ:
+		if (no_exception_p(opts)) { return sym_wait_readable; }
+                read_would_block(nonblock);
+                rb_io_wait_readable(FPTR_TO_FD(fptr));
+                continue;
+	    case SSL_ERROR_SYSCALL:
+		if (errno) rb_sys_fail(0);
+	    default:
+		ossl_raise(eSSLError, "SSL_write");
+	    }
+        }
+    }
+    else {
+	ID meth = nonblock ?
+	    rb_intern("write_nonblock") : rb_intern("syswrite");
+
+	rb_warning("SSL session is not started yet.");
+	if (nonblock)
+	    return rb_funcall(io, meth, 2, str, opts);
+	else
+	    return rb_funcall(io, meth, 1, str);
+    }
+
+  end:
+    return INT2NUM(nwrite);
+}
+
+/*
+ * call-seq:
+ *    ssl.syswrite(string) => Integer
+ *
+ * Writes +string+ to the SSL connection.
+ */
+static VALUE
+ossl_ssl_write(VALUE self, VALUE str)
+{
+    return ossl_ssl_write_internal(self, str, Qfalse);
+}
+
+/*
+ * call-seq:
+ *    ssl.syswrite_nonblock(string) => Integer
+ *
+ * Writes +string+ to the SSL connection in a non-blocking manner.  Raises an
+ * SSLError if writing would block.
+ */
+static VALUE
+ossl_ssl_write_nonblock(int argc, VALUE *argv, VALUE self)
+{
+    VALUE str, opts;
+
+    rb_scan_args(argc, argv, "1:", &str, &opts);
+
+    return ossl_ssl_write_internal(self, str, opts);
+}
+
+/*
+ * call-seq:
+ *    ssl.stop => nil
+ *
+ * Sends "close notify" to the peer and tries to shut down the SSL connection
+ * gracefully.
+ */
+static VALUE
+ossl_ssl_stop(VALUE self)
+{
+    SSL *ssl;
+    int ret;
+
+    GetSSL(self, ssl);
+    if (!ssl_started(ssl))
+	return Qnil;
+    ret = SSL_shutdown(ssl);
+    if (ret == 1) /* Have already received close_notify */
+	return Qnil;
+    if (ret == 0) /* Sent close_notify, but we don't wait for reply */
+	return Qnil;
+
+    /*
+     * XXX: Something happened. Possibly it failed because the underlying socket
+     * is not writable/readable, since it is in non-blocking mode. We should do
+     * some proper error handling using SSL_get_error() and maybe retry, but we
+     * can't block here. Give up for now.
+     */
+    ossl_clear_error();
+    return Qnil;
+}
+
+/*
+ * call-seq:
+ *    ssl.cert => cert or nil
+ *
+ * The X509 certificate for this socket endpoint.
+ */
+static VALUE
+ossl_ssl_get_cert(VALUE self)
+{
+    SSL *ssl;
+    X509 *cert = NULL;
+
+    GetSSL(self, ssl);
+
+    /*
+     * Is this OpenSSL bug? Should add a ref?
+     * TODO: Ask for.
+     */
+    cert = SSL_get_certificate(ssl); /* NO DUPs => DON'T FREE. */
+
+    if (!cert) {
+        return Qnil;
+    }
+    return ossl_x509_new(cert);
+}
+
+/*
+ * call-seq:
+ *    ssl.peer_cert => cert or nil
+ *
+ * The X509 certificate for this socket's peer.
+ */
+static VALUE
+ossl_ssl_get_peer_cert(VALUE self)
+{
+    SSL *ssl;
+    X509 *cert = NULL;
+    VALUE obj;
+
+    GetSSL(self, ssl);
+
+    cert = SSL_get_peer_certificate(ssl); /* Adds a ref => Safe to FREE. */
+
+    if (!cert) {
+        return Qnil;
+    }
+    obj = ossl_x509_new(cert);
+    X509_free(cert);
+
+    return obj;
+}
+
+/*
+ * call-seq:
+ *    ssl.peer_cert_chain => [cert, ...] or nil
+ *
+ * The X509 certificate chain for this socket's peer.
+ */
+static VALUE
+ossl_ssl_get_peer_cert_chain(VALUE self)
+{
+    SSL *ssl;
+    STACK_OF(X509) *chain;
+    X509 *cert;
+    VALUE ary;
+    int i, num;
+
+    GetSSL(self, ssl);
+
+    chain = SSL_get_peer_cert_chain(ssl);
+    if(!chain) return Qnil;
+    num = sk_X509_num(chain);
+    ary = rb_ary_new2(num);
+    for (i = 0; i < num; i++){
+	cert = sk_X509_value(chain, i);
+	rb_ary_push(ary, ossl_x509_new(cert));
+    }
+
+    return ary;
+}
+
+/*
+* call-seq:
+*    ssl.ssl_version => String
+*
+* Returns a String representing the SSL/TLS version that was negotiated
+* for the connection, for example "TLSv1.2".
+*/
+static VALUE
+ossl_ssl_get_version(VALUE self)
+{
+    SSL *ssl;
+
+    GetSSL(self, ssl);
+
+    return rb_str_new2(SSL_get_version(ssl));
+}
+
+/*
+* call-seq:
+*    ssl.cipher => [name, version, bits, alg_bits]
+*
+* The cipher being used for the current connection
+*/
+static VALUE
+ossl_ssl_get_cipher(VALUE self)
+{
+    SSL *ssl;
+    SSL_CIPHER *cipher;
+
+    GetSSL(self, ssl);
+
+    cipher = (SSL_CIPHER *)SSL_get_current_cipher(ssl);
+
+    return ossl_ssl_cipher_to_ary(cipher);
+}
+
+/*
+ * call-seq:
+ *    ssl.state => string
+ *
+ * A description of the current connection state. This is for diagnostic
+ * purposes only.
+ */
+static VALUE
+ossl_ssl_get_state(VALUE self)
+{
+    SSL *ssl;
+    VALUE ret;
+
+    GetSSL(self, ssl);
+
+    ret = rb_str_new2(SSL_state_string(ssl));
+    if (ruby_verbose) {
+        rb_str_cat2(ret, ": ");
+        rb_str_cat2(ret, SSL_state_string_long(ssl));
+    }
+    return ret;
+}
+
+/*
+ * call-seq:
+ *    ssl.pending => Integer
+ *
+ * The number of bytes that are immediately available for reading.
+ */
+static VALUE
+ossl_ssl_pending(VALUE self)
+{
+    SSL *ssl;
+
+    GetSSL(self, ssl);
+
+    return INT2NUM(SSL_pending(ssl));
+}
+
+/*
+ * call-seq:
+ *    ssl.session_reused? -> true | false
+ *
+ * Returns true if a reused session was negotiated during the handshake.
+ */
+static VALUE
+ossl_ssl_session_reused(VALUE self)
+{
+    SSL *ssl;
+
+    GetSSL(self, ssl);
+
+    return SSL_session_reused(ssl) ? Qtrue : Qfalse;
+}
+
+/*
+ * call-seq:
+ *    ssl.session = session -> session
+ *
+ * Sets the Session to be used when the connection is established.
+ */
+static VALUE
+ossl_ssl_set_session(VALUE self, VALUE arg1)
+{
+    SSL *ssl;
+    SSL_SESSION *sess;
+
+    GetSSL(self, ssl);
+    SafeGetSSLSession(arg1, sess);
+
+    if (SSL_set_session(ssl, sess) != 1)
+        ossl_raise(eSSLError, "SSL_set_session");
+
+    return arg1;
+}
+
+#ifdef HAVE_SSL_SET_TLSEXT_HOST_NAME
+/*
+ * call-seq:
+ *    ssl.hostname = hostname -> hostname
+ *
+ * Sets the server hostname used for SNI. This needs to be set before
+ * SSLSocket#connect.
+ */
+static VALUE
+ossl_ssl_set_hostname(VALUE self, VALUE arg)
+{
+    SSL *ssl;
+    char *hostname = NULL;
+
+    GetSSL(self, ssl);
+
+    if (!NIL_P(arg))
+	hostname = StringValueCStr(arg);
+
+    if (!SSL_set_tlsext_host_name(ssl, hostname))
+	ossl_raise(eSSLError, NULL);
+
+    /* for SSLSocket#hostname */
+    rb_ivar_set(self, id_i_hostname, arg);
+
+    return arg;
+}
+#endif
+
+/*
+ * call-seq:
+ *    ssl.verify_result => Integer
+ *
+ * Returns the result of the peer certificates verification.  See verify(1)
+ * for error values and descriptions.
+ *
+ * If no peer certificate was presented X509_V_OK is returned.
+ */
+static VALUE
+ossl_ssl_get_verify_result(VALUE self)
+{
+    SSL *ssl;
+
+    GetSSL(self, ssl);
+
+    return INT2NUM(SSL_get_verify_result(ssl));
+}
+
+/*
+ * call-seq:
+ *    ssl.client_ca => [x509name, ...]
+ *
+ * Returns the list of client CAs. Please note that in contrast to
+ * SSLContext#client_ca= no array of X509::Certificate is returned but
+ * X509::Name instances of the CA's subject distinguished name.
+ *
+ * In server mode, returns the list set by SSLContext#client_ca=.
+ * In client mode, returns the list of client CAs sent from the server.
+ */
+static VALUE
+ossl_ssl_get_client_ca_list(VALUE self)
+{
+    SSL *ssl;
+    STACK_OF(X509_NAME) *ca;
+
+    GetSSL(self, ssl);
+
+    ca = SSL_get_client_CA_list(ssl);
+    return ossl_x509name_sk2ary(ca);
+}
+
+# ifdef HAVE_SSL_CTX_SET_NEXT_PROTO_SELECT_CB
+/*
+ * call-seq:
+ *    ssl.npn_protocol => String | nil
+ *
+ * Returns the protocol string that was finally selected by the client
+ * during the handshake.
+ */
+static VALUE
+ossl_ssl_npn_protocol(VALUE self)
+{
+    SSL *ssl;
+    const unsigned char *out;
+    unsigned int outlen;
+
+    GetSSL(self, ssl);
+
+    SSL_get0_next_proto_negotiated(ssl, &out, &outlen);
+    if (!outlen)
+	return Qnil;
+    else
+	return rb_str_new((const char *) out, outlen);
+}
+# endif
+
+# ifdef HAVE_SSL_CTX_SET_ALPN_SELECT_CB
+/*
+ * call-seq:
+ *    ssl.alpn_protocol => String | nil
+ *
+ * Returns the ALPN protocol string that was finally selected by the server
+ * during the handshake.
+ */
+static VALUE
+ossl_ssl_alpn_protocol(VALUE self)
+{
+    SSL *ssl;
+    const unsigned char *out;
+    unsigned int outlen;
+
+    GetSSL(self, ssl);
+
+    SSL_get0_alpn_selected(ssl, &out, &outlen);
+    if (!outlen)
+	return Qnil;
+    else
+	return rb_str_new((const char *) out, outlen);
+}
+# endif
+
+# ifdef HAVE_SSL_GET_SERVER_TMP_KEY
+/*
+ * call-seq:
+ *    ssl.tmp_key => PKey or nil
+ *
+ * Returns the ephemeral key used in case of forward secrecy cipher.
+ */
+static VALUE
+ossl_ssl_tmp_key(VALUE self)
+{
+    SSL *ssl;
+    EVP_PKEY *key;
+
+    GetSSL(self, ssl);
+    if (!SSL_get_server_tmp_key(ssl, &key))
+	return Qnil;
+    return ossl_pkey_new(key);
+}
+# endif /* defined(HAVE_SSL_GET_SERVER_TMP_KEY) */
+#endif /* !defined(OPENSSL_NO_SOCK) */
+
+#undef rb_intern
+#define rb_intern(s) rb_intern_const(s)
+void
+Init_ossl_ssl(void)
+{
+    int i;
+    VALUE ary;
+
+#if 0
+    mOSSL = rb_define_module("OpenSSL");
+    eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError);
+    rb_mWaitReadable = rb_define_module_under(rb_cIO, "WaitReadable");
+    rb_mWaitWritable = rb_define_module_under(rb_cIO, "WaitWritable");
+#endif
+
+    ID_callback_state = rb_intern("callback_state");
+
+    ossl_ssl_ex_vcb_idx = SSL_get_ex_new_index(0,(void *)"ossl_ssl_ex_vcb_idx",0,0,0);
+    ossl_ssl_ex_store_p = SSL_get_ex_new_index(0,(void *)"ossl_ssl_ex_store_p",0,0,0);
+    ossl_ssl_ex_ptr_idx = SSL_get_ex_new_index(0,(void *)"ossl_ssl_ex_ptr_idx",0,0,0);
+
+    /* Document-module: OpenSSL::SSL
+     *
+     * Use SSLContext to set up the parameters for a TLS (former SSL)
+     * connection. Both client and server TLS connections are supported,
+     * SSLSocket and SSLServer may be used in conjunction with an instance
+     * of SSLContext to set up connections.
+     */
+    mSSL = rb_define_module_under(mOSSL, "SSL");
+
+    /* Document-module: OpenSSL::ExtConfig
+     *
+     * This module contains configuration information about the SSL extension,
+     * for example if socket support is enabled, or the host name TLS extension
+     * is enabled.  Constants in this module will always be defined, but contain
+     * `true` or `false` values depending on the configuration of your OpenSSL
+     * installation.
+     */
+    mSSLExtConfig = rb_define_module_under(mOSSL, "ExtConfig");
+
+    /* Document-class: OpenSSL::SSL::SSLError
+     *
+     * Generic error class raised by SSLSocket and SSLContext.
+     */
+    eSSLError = rb_define_class_under(mSSL, "SSLError", eOSSLError);
+    eSSLErrorWaitReadable = rb_define_class_under(mSSL, "SSLErrorWaitReadable", eSSLError);
+    rb_include_module(eSSLErrorWaitReadable, rb_mWaitReadable);
+    eSSLErrorWaitWritable = rb_define_class_under(mSSL, "SSLErrorWaitWritable", eSSLError);
+    rb_include_module(eSSLErrorWaitWritable, rb_mWaitWritable);
+
+    Init_ossl_ssl_session();
+
+    /* Document-class: OpenSSL::SSL::SSLContext
+     *
+     * An SSLContext is used to set various options regarding certificates,
+     * algorithms, verification, session caching, etc.  The SSLContext is
+     * used to create an SSLSocket.
+     *
+     * All attributes must be set before creating an SSLSocket as the
+     * SSLContext will be frozen afterward.
+     */
+    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
+     */
+    rb_attr(cSSLContext, rb_intern("cert"), 1, 1, Qfalse);
+
+    /*
+     * Context private key
+     */
+    rb_attr(cSSLContext, rb_intern("key"), 1, 1, Qfalse);
+
+    /*
+     * A certificate or Array of certificates that will be sent to the client.
+     */
+    rb_attr(cSSLContext, rb_intern("client_ca"), 1, 1, Qfalse);
+
+    /*
+     * The path to a file containing a PEM-format CA certificate
+     */
+    rb_attr(cSSLContext, rb_intern("ca_file"), 1, 1, Qfalse);
+
+    /*
+     * The path to a directory containing CA certificates in PEM format.
+     *
+     * Files are looked up by subject's X509 name's hash value.
+     */
+    rb_attr(cSSLContext, rb_intern("ca_path"), 1, 1, Qfalse);
+
+    /*
+     * Maximum session lifetime in seconds.
+     */
+    rb_attr(cSSLContext, rb_intern("timeout"), 1, 1, Qfalse);
+
+    /*
+     * Session verification mode.
+     *
+     * Valid modes are VERIFY_NONE, VERIFY_PEER, VERIFY_CLIENT_ONCE,
+     * VERIFY_FAIL_IF_NO_PEER_CERT and defined on OpenSSL::SSL
+     *
+     * The default mode is VERIFY_NONE, which does not perform any verification
+     * at all.
+     *
+     * See SSL_CTX_set_verify(3) for details.
+     */
+    rb_attr(cSSLContext, rb_intern("verify_mode"), 1, 1, Qfalse);
+
+    /*
+     * Number of CA certificates to walk when verifying a certificate chain.
+     */
+    rb_attr(cSSLContext, rb_intern("verify_depth"), 1, 1, Qfalse);
+
+    /*
+     * A callback for additional certificate verification.  The callback is
+     * invoked for each certificate in the chain.
+     *
+     * The callback is invoked with two values.  +preverify_ok+ indicates
+     * indicates if the verification was passed (true) or not (false).
+     * +store_context+ is an OpenSSL::X509::StoreContext containing the
+     * context used for certificate verification.
+     *
+     * If the callback returns false, the chain verification is immediately
+     * stopped and a bad_certificate alert is then sent.
+     */
+    rb_attr(cSSLContext, rb_intern("verify_callback"), 1, 1, Qfalse);
+
+    /*
+     * Whether to check the server certificate is valid for the hostname.
+     *
+     * In order to make this work, verify_mode must be set to VERIFY_PEER and
+     * the server hostname must be given by OpenSSL::SSL::SSLSocket#hostname=.
+     */
+    rb_attr(cSSLContext, rb_intern("verify_hostname"), 1, 1, Qfalse);
+
+    /*
+     * An OpenSSL::X509::Store used for certificate verification.
+     */
+    rb_attr(cSSLContext, rb_intern("cert_store"), 1, 1, Qfalse);
+
+    /*
+     * An Array of extra X509 certificates to be added to the certificate
+     * chain.
+     */
+    rb_attr(cSSLContext, rb_intern("extra_chain_cert"), 1, 1, Qfalse);
+
+    /*
+     * A callback invoked when a client certificate is requested by a server
+     * and no certificate has been set.
+     *
+     * The callback is invoked with a Session and must return an Array
+     * containing an OpenSSL::X509::Certificate and an OpenSSL::PKey.  If any
+     * other value is returned the handshake is suspended.
+     */
+    rb_attr(cSSLContext, rb_intern("client_cert_cb"), 1, 1, Qfalse);
+
+#if !defined(OPENSSL_NO_EC) && defined(HAVE_SSL_CTX_SET_TMP_ECDH_CALLBACK)
+    /*
+     * A callback invoked when ECDH parameters are required.
+     *
+     * The callback is invoked with the Session for the key exchange, an
+     * flag indicating the use of an export cipher and the keylength
+     * required.
+     *
+     * The callback is deprecated. This does not work with recent versions of
+     * OpenSSL. Use OpenSSL::SSL::SSLContext#ecdh_curves= instead.
+     */
+    rb_attr(cSSLContext, rb_intern("tmp_ecdh_callback"), 1, 1, Qfalse);
+#endif
+
+    /*
+     * Sets the context in which a session can be reused.  This allows
+     * sessions for multiple applications to be distinguished, for example, by
+     * name.
+     */
+    rb_attr(cSSLContext, rb_intern("session_id_context"), 1, 1, Qfalse);
+
+    /*
+     * A callback invoked on a server when a session is proposed by the client
+     * but the session could not be found in the server's internal cache.
+     *
+     * The callback is invoked with the SSLSocket and session id.  The
+     * callback may return a Session from an external cache.
+     */
+    rb_attr(cSSLContext, rb_intern("session_get_cb"), 1, 1, Qfalse);
+
+    /*
+     * A callback invoked when a new session was negotiated.
+     *
+     * The callback is invoked with an SSLSocket.  If false is returned the
+     * session will be removed from the internal cache.
+     */
+    rb_attr(cSSLContext, rb_intern("session_new_cb"), 1, 1, Qfalse);
+
+    /*
+     * A callback invoked when a session is removed from the internal cache.
+     *
+     * The callback is invoked with an SSLContext and a Session.
+     */
+    rb_attr(cSSLContext, rb_intern("session_remove_cb"), 1, 1, Qfalse);
+
+#ifdef HAVE_SSL_SET_TLSEXT_HOST_NAME
+    rb_define_const(mSSLExtConfig, "HAVE_TLSEXT_HOST_NAME", Qtrue);
+#else
+    rb_define_const(mSSLExtConfig, "HAVE_TLSEXT_HOST_NAME", Qfalse);
+#endif
+
+#ifdef TLS_DH_anon_WITH_AES_256_GCM_SHA384
+    rb_define_const(mSSLExtConfig, "TLS_DH_anon_WITH_AES_256_GCM_SHA384", Qtrue);
+#else
+    rb_define_const(mSSLExtConfig, "TLS_DH_anon_WITH_AES_256_GCM_SHA384", Qfalse);
+#endif
+
+    /*
+     * A callback invoked whenever a new handshake is initiated. May be used
+     * to disable renegotiation entirely.
+     *
+     * The callback is invoked with the active SSLSocket. The callback's
+     * return value is irrelevant, normal return indicates "approval" of the
+     * renegotiation and will continue the process. To forbid renegotiation
+     * and to cancel the process, an Error may be raised within the callback.
+     *
+     * === Disable client renegotiation
+     *
+     * When running a server, it is often desirable to disable client
+     * renegotiation entirely. You may use a callback as follows to implement
+     * this feature:
+     *
+     *   num_handshakes = 0
+     *   ctx.renegotiation_cb = lambda do |ssl|
+     *     num_handshakes += 1
+     *     raise RuntimeError.new("Client renegotiation disabled") if num_handshakes > 1
+     *   end
+     */
+    rb_attr(cSSLContext, rb_intern("renegotiation_cb"), 1, 1, Qfalse);
+#ifdef HAVE_SSL_CTX_SET_NEXT_PROTO_SELECT_CB
+    /*
+     * An Enumerable of Strings. Each String represents a protocol to be
+     * advertised as the list of supported protocols for Next Protocol
+     * Negotiation. Supported in OpenSSL 1.0.1 and higher. Has no effect
+     * on the client side. If not set explicitly, the NPN extension will
+     * not be sent by the server in the handshake.
+     *
+     * === Example
+     *
+     *   ctx.npn_protocols = ["http/1.1", "spdy/2"]
+     */
+    rb_attr(cSSLContext, rb_intern("npn_protocols"), 1, 1, Qfalse);
+    /*
+     * A callback invoked on the client side when the client needs to select
+     * a protocol from the list sent by the server. Supported in OpenSSL 1.0.1
+     * and higher. The client MUST select a protocol of those advertised by
+     * the server. If none is acceptable, raising an error in the callback
+     * will cause the handshake to fail. Not setting this callback explicitly
+     * means not supporting the NPN extension on the client - any protocols
+     * advertised by the server will be ignored.
+     *
+     * === Example
+     *
+     *   ctx.npn_select_cb = lambda do |protocols|
+     *     # inspect the protocols and select one
+     *     protocols.first
+     *   end
+     */
+    rb_attr(cSSLContext, rb_intern("npn_select_cb"), 1, 1, Qfalse);
+#endif
+
+#ifdef HAVE_SSL_CTX_SET_ALPN_SELECT_CB
+    /*
+     * An Enumerable of Strings. Each String represents a protocol to be
+     * advertised as the list of supported protocols for Application-Layer
+     * Protocol Negotiation. Supported in OpenSSL 1.0.2 and higher. Has no
+     * effect on the server side. If not set explicitly, the ALPN extension will
+     * not be included in the handshake.
+     *
+     * === Example
+     *
+     *   ctx.alpn_protocols = ["http/1.1", "spdy/2", "h2"]
+     */
+    rb_attr(cSSLContext, rb_intern("alpn_protocols"), 1, 1, Qfalse);
+    /*
+     * A callback invoked on the server side when the server needs to select
+     * a protocol from the list sent by the client. Supported in OpenSSL 1.0.2
+     * and higher. The callback must return a protocol of those advertised by
+     * the client. If none is acceptable, raising an error in the callback
+     * will cause the handshake to fail. Not setting this callback explicitly
+     * means not supporting the ALPN extension on the server - any protocols
+     * advertised by the client will be ignored.
+     *
+     * === Example
+     *
+     *   ctx.alpn_select_cb = lambda do |protocols|
+     *     # inspect the protocols and select one
+     *     protocols.first
+     *   end
+     */
+    rb_attr(cSSLContext, rb_intern("alpn_select_cb"), 1, 1, Qfalse);
+#endif
+
+    rb_define_alias(cSSLContext, "ssl_timeout", "timeout");
+    rb_define_alias(cSSLContext, "ssl_timeout=", "timeout=");
+    rb_define_method(cSSLContext, "ssl_version=", ossl_sslctx_set_ssl_version, 1);
+    rb_define_method(cSSLContext, "ciphers",     ossl_sslctx_get_ciphers, 0);
+    rb_define_method(cSSLContext, "ciphers=",    ossl_sslctx_set_ciphers, 1);
+    rb_define_method(cSSLContext, "ecdh_curves=", ossl_sslctx_set_ecdh_curves, 1);
+    rb_define_method(cSSLContext, "security_level", ossl_sslctx_get_security_level, 0);
+    rb_define_method(cSSLContext, "security_level=", ossl_sslctx_set_security_level, 1);
+
+    rb_define_method(cSSLContext, "setup", ossl_sslctx_setup, 0);
+    rb_define_alias(cSSLContext, "freeze", "setup");
+
+    /*
+     * No session caching for client or server
+     */
+    rb_define_const(cSSLContext, "SESSION_CACHE_OFF", LONG2NUM(SSL_SESS_CACHE_OFF));
+
+    /*
+     * Client sessions are added to the session cache
+     */
+    rb_define_const(cSSLContext, "SESSION_CACHE_CLIENT", LONG2NUM(SSL_SESS_CACHE_CLIENT)); /* doesn't actually do anything in 0.9.8e */
+
+    /*
+     * Server sessions are added to the session cache
+     */
+    rb_define_const(cSSLContext, "SESSION_CACHE_SERVER", LONG2NUM(SSL_SESS_CACHE_SERVER));
+
+    /*
+     * Both client and server sessions are added to the session cache
+     */
+    rb_define_const(cSSLContext, "SESSION_CACHE_BOTH", LONG2NUM(SSL_SESS_CACHE_BOTH)); /* no different than CACHE_SERVER in 0.9.8e */
+
+    /*
+     * Normally the session cache is checked for expired sessions every 255
+     * connections.  Since this may lead to a delay that cannot be controlled,
+     * the automatic flushing may be disabled and #flush_sessions can be
+     * called explicitly.
+     */
+    rb_define_const(cSSLContext, "SESSION_CACHE_NO_AUTO_CLEAR", LONG2NUM(SSL_SESS_CACHE_NO_AUTO_CLEAR));
+
+    /*
+     * Always perform external lookups of sessions even if they are in the
+     * internal cache.
+     *
+     * This flag has no effect on clients
+     */
+    rb_define_const(cSSLContext, "SESSION_CACHE_NO_INTERNAL_LOOKUP", LONG2NUM(SSL_SESS_CACHE_NO_INTERNAL_LOOKUP));
+
+    /*
+     * Never automatically store sessions in the internal store.
+     */
+    rb_define_const(cSSLContext, "SESSION_CACHE_NO_INTERNAL_STORE", LONG2NUM(SSL_SESS_CACHE_NO_INTERNAL_STORE));
+
+    /*
+     * Enables both SESSION_CACHE_NO_INTERNAL_LOOKUP and
+     * SESSION_CACHE_NO_INTERNAL_STORE.
+     */
+    rb_define_const(cSSLContext, "SESSION_CACHE_NO_INTERNAL", LONG2NUM(SSL_SESS_CACHE_NO_INTERNAL));
+
+    rb_define_method(cSSLContext, "session_add",     ossl_sslctx_session_add, 1);
+    rb_define_method(cSSLContext, "session_remove",     ossl_sslctx_session_remove, 1);
+    rb_define_method(cSSLContext, "session_cache_mode",     ossl_sslctx_get_session_cache_mode, 0);
+    rb_define_method(cSSLContext, "session_cache_mode=",     ossl_sslctx_set_session_cache_mode, 1);
+    rb_define_method(cSSLContext, "session_cache_size",     ossl_sslctx_get_session_cache_size, 0);
+    rb_define_method(cSSLContext, "session_cache_size=",     ossl_sslctx_set_session_cache_size, 1);
+    rb_define_method(cSSLContext, "session_cache_stats",     ossl_sslctx_get_session_cache_stats, 0);
+    rb_define_method(cSSLContext, "flush_sessions",     ossl_sslctx_flush_sessions, -1);
+    rb_define_method(cSSLContext, "options",     ossl_sslctx_get_options, 0);
+    rb_define_method(cSSLContext, "options=",     ossl_sslctx_set_options, 1);
+
+    ary = rb_ary_new2(numberof(ossl_ssl_method_tab));
+    for (i = 0; i < numberof(ossl_ssl_method_tab); i++) {
+        rb_ary_push(ary, ID2SYM(rb_intern(ossl_ssl_method_tab[i].name)));
+    }
+    rb_obj_freeze(ary);
+    /* The list of available SSL/TLS methods */
+    rb_define_const(cSSLContext, "METHODS", ary);
+
+    /*
+     * Document-class: OpenSSL::SSL::SSLSocket
+     */
+    cSSLSocket = rb_define_class_under(mSSL, "SSLSocket", rb_cObject);
+#ifdef OPENSSL_NO_SOCK
+    rb_define_const(mSSLExtConfig, "OPENSSL_NO_SOCK", Qtrue);
+    rb_define_method(cSSLSocket, "initialize", rb_f_notimplement, -1);
+#else
+    rb_define_const(mSSLExtConfig, "OPENSSL_NO_SOCK", Qfalse);
+    rb_define_alloc_func(cSSLSocket, ossl_ssl_s_alloc);
+    rb_define_method(cSSLSocket, "initialize", ossl_ssl_initialize, -1);
+    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);
+    rb_define_method(cSSLSocket, "accept_nonblock", ossl_ssl_accept_nonblock, -1);
+    rb_define_method(cSSLSocket, "sysread",    ossl_ssl_read, -1);
+    rb_define_private_method(cSSLSocket, "sysread_nonblock",    ossl_ssl_read_nonblock, -1);
+    rb_define_method(cSSLSocket, "syswrite",   ossl_ssl_write, 1);
+    rb_define_private_method(cSSLSocket, "syswrite_nonblock",    ossl_ssl_write_nonblock, -1);
+    rb_define_private_method(cSSLSocket, "stop",   ossl_ssl_stop, 0);
+    rb_define_method(cSSLSocket, "cert",       ossl_ssl_get_cert, 0);
+    rb_define_method(cSSLSocket, "peer_cert",  ossl_ssl_get_peer_cert, 0);
+    rb_define_method(cSSLSocket, "peer_cert_chain", ossl_ssl_get_peer_cert_chain, 0);
+    rb_define_method(cSSLSocket, "ssl_version",    ossl_ssl_get_version, 0);
+    rb_define_method(cSSLSocket, "cipher",     ossl_ssl_get_cipher, 0);
+    rb_define_method(cSSLSocket, "state",      ossl_ssl_get_state, 0);
+    rb_define_method(cSSLSocket, "pending",    ossl_ssl_pending, 0);
+    rb_define_method(cSSLSocket, "session_reused?",    ossl_ssl_session_reused, 0);
+    /* implementation of OpenSSL::SSL::SSLSocket#session is in lib/openssl/ssl.rb */
+    rb_define_method(cSSLSocket, "session=",    ossl_ssl_set_session, 1);
+    rb_define_method(cSSLSocket, "verify_result", ossl_ssl_get_verify_result, 0);
+    rb_define_method(cSSLSocket, "client_ca", ossl_ssl_get_client_ca_list, 0);
+#ifdef HAVE_SSL_SET_TLSEXT_HOST_NAME
+    /* #hostname is defined in lib/openssl/ssl.rb */
+    rb_define_method(cSSLSocket, "hostname=", ossl_ssl_set_hostname, 1);
+#endif
+# ifdef HAVE_SSL_GET_SERVER_TMP_KEY
+    rb_define_method(cSSLSocket, "tmp_key", ossl_ssl_tmp_key, 0);
+# endif
+# ifdef HAVE_SSL_CTX_SET_ALPN_SELECT_CB
+    rb_define_method(cSSLSocket, "alpn_protocol", ossl_ssl_alpn_protocol, 0);
+# endif
+# ifdef HAVE_SSL_CTX_SET_NEXT_PROTO_SELECT_CB
+    rb_define_method(cSSLSocket, "npn_protocol", ossl_ssl_npn_protocol, 0);
+# endif
+#endif
+
+#define ossl_ssl_def_const(x) rb_define_const(mSSL, #x, LONG2NUM(SSL_##x))
+
+    ossl_ssl_def_const(VERIFY_NONE);
+    ossl_ssl_def_const(VERIFY_PEER);
+    ossl_ssl_def_const(VERIFY_FAIL_IF_NO_PEER_CERT);
+    ossl_ssl_def_const(VERIFY_CLIENT_ONCE);
+    /* Introduce constants included in OP_ALL.  These constants are mostly for
+     * unset some bits in OP_ALL such as;
+     *   ctx.options = OP_ALL & ~OP_DONT_INSERT_EMPTY_FRAGMENTS
+     */
+    ossl_ssl_def_const(OP_MICROSOFT_SESS_ID_BUG);
+    ossl_ssl_def_const(OP_NETSCAPE_CHALLENGE_BUG);
+    ossl_ssl_def_const(OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG);
+    ossl_ssl_def_const(OP_SSLREF2_REUSE_CERT_TYPE_BUG);
+    ossl_ssl_def_const(OP_MICROSOFT_BIG_SSLV3_BUFFER);
+    ossl_ssl_def_const(OP_MSIE_SSLV2_RSA_PADDING);
+    ossl_ssl_def_const(OP_SSLEAY_080_CLIENT_DH_BUG);
+    ossl_ssl_def_const(OP_TLS_D5_BUG);
+    ossl_ssl_def_const(OP_TLS_BLOCK_PADDING_BUG);
+    ossl_ssl_def_const(OP_DONT_INSERT_EMPTY_FRAGMENTS);
+    ossl_ssl_def_const(OP_ALL);
+    ossl_ssl_def_const(OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
+    ossl_ssl_def_const(OP_SINGLE_ECDH_USE);
+    ossl_ssl_def_const(OP_SINGLE_DH_USE);
+    ossl_ssl_def_const(OP_EPHEMERAL_RSA);
+    ossl_ssl_def_const(OP_CIPHER_SERVER_PREFERENCE);
+    ossl_ssl_def_const(OP_TLS_ROLLBACK_BUG);
+    ossl_ssl_def_const(OP_NO_SSLv2);
+    ossl_ssl_def_const(OP_NO_SSLv3);
+    ossl_ssl_def_const(OP_NO_TLSv1);
+#if defined(SSL_OP_NO_TLSv1_1)
+    ossl_ssl_def_const(OP_NO_TLSv1_1);
+#endif
+#if defined(SSL_OP_NO_TLSv1_2)
+    ossl_ssl_def_const(OP_NO_TLSv1_2);
+#endif
+#if defined(SSL_OP_NO_TICKET)
+    ossl_ssl_def_const(OP_NO_TICKET);
+#endif
+#if defined(SSL_OP_NO_COMPRESSION)
+    ossl_ssl_def_const(OP_NO_COMPRESSION);
+#endif
+    ossl_ssl_def_const(OP_PKCS1_CHECK_1);
+    ossl_ssl_def_const(OP_PKCS1_CHECK_2);
+    ossl_ssl_def_const(OP_NETSCAPE_CA_DN_BUG);
+    ossl_ssl_def_const(OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG);
+
+    sym_exception = ID2SYM(rb_intern("exception"));
+    sym_wait_readable = ID2SYM(rb_intern("wait_readable"));
+    sym_wait_writable = ID2SYM(rb_intern("wait_writable"));
+
+    id_tmp_dh_callback = rb_intern("tmp_dh_callback");
+    id_tmp_ecdh_callback = rb_intern("tmp_ecdh_callback");
+    id_npn_protocols_encoded = rb_intern("npn_protocols_encoded");
+
+#define DefIVarID(name) do \
+    id_i_##name = rb_intern("@"#name); while (0)
+
+    DefIVarID(cert_store);
+    DefIVarID(ca_file);
+    DefIVarID(ca_path);
+    DefIVarID(verify_mode);
+    DefIVarID(verify_depth);
+    DefIVarID(verify_callback);
+    DefIVarID(client_ca);
+    DefIVarID(renegotiation_cb);
+    DefIVarID(cert);
+    DefIVarID(key);
+    DefIVarID(extra_chain_cert);
+    DefIVarID(client_cert_cb);
+    DefIVarID(tmp_ecdh_callback);
+    DefIVarID(timeout);
+    DefIVarID(session_id_context);
+    DefIVarID(session_get_cb);
+    DefIVarID(session_new_cb);
+    DefIVarID(session_remove_cb);
+    DefIVarID(npn_select_cb);
+    DefIVarID(npn_protocols);
+    DefIVarID(alpn_protocols);
+    DefIVarID(alpn_select_cb);
+    DefIVarID(servername_cb);
+    DefIVarID(verify_hostname);
+
+    DefIVarID(io);
+    DefIVarID(context);
+    DefIVarID(hostname);
+}
diff --git a/ext/openssl/ossl_ssl.h b/ext/openssl/ossl_ssl.h
new file mode 100644
index 0000000..c1a3cd6
--- /dev/null
+++ b/ext/openssl/ossl_ssl.h
@@ -0,0 +1,41 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2001-2002  Michal Rokos <m.rokos at sh.cvut.cz>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#if !defined(_OSSL_SSL_H_)
+#define _OSSL_SSL_H_
+
+#define GetSSL(obj, ssl) do { \
+	TypedData_Get_Struct((obj), SSL, &ossl_ssl_type, (ssl)); \
+	if (!(ssl)) { \
+		ossl_raise(rb_eRuntimeError, "SSL is not initialized"); \
+	} \
+} while (0)
+
+#define GetSSLSession(obj, sess) do { \
+	TypedData_Get_Struct((obj), SSL_SESSION, &ossl_ssl_session_type, (sess)); \
+	if (!(sess)) { \
+		ossl_raise(rb_eRuntimeError, "SSL Session wasn't initialized."); \
+	} \
+} while (0)
+
+#define SafeGetSSLSession(obj, sess) do { \
+	OSSL_Check_Kind((obj), cSSLSession); \
+	GetSSLSession((obj), (sess)); \
+} while (0)
+
+extern const rb_data_type_t ossl_ssl_type;
+extern const rb_data_type_t ossl_ssl_session_type;
+extern VALUE mSSL;
+extern VALUE cSSLSocket;
+extern VALUE cSSLSession;
+
+void Init_ossl_ssl(void);
+void Init_ossl_ssl_session(void);
+
+#endif /* _OSSL_SSL_H_ */
diff --git a/ext/openssl/ossl_ssl_session.c b/ext/openssl/ossl_ssl_session.c
new file mode 100644
index 0000000..1b602a6
--- /dev/null
+++ b/ext/openssl/ossl_ssl_session.c
@@ -0,0 +1,334 @@
+/*
+ *  Copyright (C) 2004-2007 Technorama Ltd. <oss-ruby at technorama.net>
+ */
+
+#include "ossl.h"
+
+VALUE cSSLSession;
+static VALUE eSSLSession;
+
+static void
+ossl_ssl_session_free(void *ptr)
+{
+    SSL_SESSION_free(ptr);
+}
+
+const rb_data_type_t ossl_ssl_session_type = {
+    "OpenSSL/SSL/Session",
+    {
+	0, ossl_ssl_session_free,
+    },
+    0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
+};
+
+static VALUE ossl_ssl_session_alloc(VALUE klass)
+{
+	return TypedData_Wrap_Struct(klass, &ossl_ssl_session_type, NULL);
+}
+
+/*
+ * call-seq:
+ *   Session.new(ssl_socket) -> Session
+ *   Session.new(string) -> Session
+ *
+ * Creates a new Session object from an instance of SSLSocket or DER/PEM encoded
+ * String.
+ */
+static VALUE ossl_ssl_session_initialize(VALUE self, VALUE arg1)
+{
+	SSL_SESSION *ctx = NULL;
+
+	if (RDATA(self)->data)
+		ossl_raise(eSSLSession, "SSL Session already initialized");
+
+	if (rb_obj_is_instance_of(arg1, cSSLSocket)) {
+		SSL *ssl;
+
+		GetSSL(arg1, ssl);
+
+		if ((ctx = SSL_get1_session(ssl)) == NULL)
+			ossl_raise(eSSLSession, "no session available");
+	} else {
+		BIO *in = ossl_obj2bio(arg1);
+
+		ctx = PEM_read_bio_SSL_SESSION(in, NULL, NULL, NULL);
+
+		if (!ctx) {
+		        OSSL_BIO_reset(in);
+			ctx = d2i_SSL_SESSION_bio(in, NULL);
+		}
+
+		BIO_free(in);
+
+		if (!ctx)
+			ossl_raise(rb_eArgError, "unknown type");
+	}
+
+	/* should not happen */
+	if (ctx == NULL)
+		ossl_raise(eSSLSession, "ctx not set - internal error");
+
+	RDATA(self)->data = ctx;
+
+	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 !defined(HAVE_SSL_SESSION_CMP)
+int ossl_SSL_SESSION_cmp(const SSL_SESSION *a, const SSL_SESSION *b)
+{
+    unsigned int a_len;
+    const unsigned char *a_sid = SSL_SESSION_get_id(a, &a_len);
+    unsigned int b_len;
+    const unsigned char *b_sid = SSL_SESSION_get_id(b, &b_len);
+
+    if (SSL_SESSION_get_protocol_version(a) != SSL_SESSION_get_protocol_version(b))
+	return 1;
+    if (a_len != b_len)
+	return 1;
+
+    return CRYPTO_memcmp(a_sid, b_sid, a_len);
+}
+#define SSL_SESSION_cmp(a, b) ossl_SSL_SESSION_cmp(a, b)
+#endif
+
+/*
+ * call-seq:
+ *   session1 == session2 -> boolean
+ *
+ * Returns true if the two Session is the same, false if not.
+ */
+static VALUE ossl_ssl_session_eq(VALUE val1, VALUE val2)
+{
+	SSL_SESSION *ctx1, *ctx2;
+
+	GetSSLSession(val1, ctx1);
+	SafeGetSSLSession(val2, ctx2);
+
+	switch (SSL_SESSION_cmp(ctx1, ctx2)) {
+	case 0:		return Qtrue;
+	default:	return Qfalse;
+	}
+}
+
+/*
+ * call-seq:
+ *    session.time -> Time
+ *
+ * Returns the time at which the session was established.
+ */
+static VALUE
+ossl_ssl_session_get_time(VALUE self)
+{
+    SSL_SESSION *ctx;
+    long t;
+
+    GetSSLSession(self, ctx);
+    t = SSL_SESSION_get_time(ctx);
+    if (t == 0)
+	return Qnil;
+
+    return rb_funcall(rb_cTime, rb_intern("at"), 1, LONG2NUM(t));
+}
+
+/*
+ * call-seq:
+ *    session.timeout -> Integer
+ *
+ * Returns the timeout value set for the session, in seconds from the
+ * established time.
+ *
+ */
+static VALUE
+ossl_ssl_session_get_timeout(VALUE self)
+{
+    SSL_SESSION *ctx;
+    long t;
+
+    GetSSLSession(self, ctx);
+    t = SSL_SESSION_get_timeout(ctx);
+
+    return LONG2NUM(t);
+}
+
+/*
+ * call-seq:
+ *    session.time = time
+ *    session.time = integer
+ *
+ * Sets start time of the session. Time resolution is in seconds.
+ *
+ */
+static VALUE ossl_ssl_session_set_time(VALUE self, VALUE time_v)
+{
+	SSL_SESSION *ctx;
+	long t;
+
+	GetSSLSession(self, ctx);
+	if (rb_obj_is_instance_of(time_v, rb_cTime)) {
+		time_v = rb_funcall(time_v, rb_intern("to_i"), 0);
+	}
+	t = NUM2LONG(time_v);
+	SSL_SESSION_set_time(ctx, t);
+	return ossl_ssl_session_get_time(self);
+}
+
+/*
+ * call-seq:
+ *    session.timeout = integer
+ *
+ * Sets how long until the session expires in seconds.
+ */
+static VALUE ossl_ssl_session_set_timeout(VALUE self, VALUE time_v)
+{
+	SSL_SESSION *ctx;
+	long t;
+
+	GetSSLSession(self, ctx);
+	t = NUM2LONG(time_v);
+	SSL_SESSION_set_timeout(ctx, t);
+	return ossl_ssl_session_get_timeout(self);
+}
+
+/*
+ * call-seq:
+ *    session.id -> String
+ *
+ * Returns the Session ID.
+*/
+static VALUE ossl_ssl_session_get_id(VALUE self)
+{
+	SSL_SESSION *ctx;
+	const unsigned char *p = NULL;
+	unsigned int i = 0;
+
+	GetSSLSession(self, ctx);
+
+	p = SSL_SESSION_get_id(ctx, &i);
+
+	return rb_str_new((const char *) p, i);
+}
+
+/*
+ * call-seq:
+ *    session.to_der -> String
+ *
+ * Returns an ASN1 encoded String that contains the Session object.
+ */
+static VALUE ossl_ssl_session_to_der(VALUE self)
+{
+	SSL_SESSION *ctx;
+	unsigned char *p;
+	int len;
+	VALUE str;
+
+	GetSSLSession(self, ctx);
+	len = i2d_SSL_SESSION(ctx, NULL);
+	if (len <= 0) {
+		ossl_raise(eSSLSession, "i2d_SSL_SESSION");
+	}
+
+	str = rb_str_new(0, len);
+	p = (unsigned char *)RSTRING_PTR(str);
+	i2d_SSL_SESSION(ctx, &p);
+	ossl_str_adjust(str, p);
+	return str;
+}
+
+/*
+ * call-seq:
+ *    session.to_pem -> String
+ *
+ * Returns a PEM encoded String that contains the Session object.
+ */
+static VALUE ossl_ssl_session_to_pem(VALUE self)
+{
+	SSL_SESSION *ctx;
+	BIO *out;
+
+	GetSSLSession(self, ctx);
+
+	if (!(out = BIO_new(BIO_s_mem()))) {
+		ossl_raise(eSSLSession, "BIO_s_mem()");
+	}
+
+	if (!PEM_write_bio_SSL_SESSION(out, ctx)) {
+		BIO_free(out);
+		ossl_raise(eSSLSession, "SSL_SESSION_print()");
+	}
+
+
+	return ossl_membio2str(out);
+}
+
+
+/*
+ * call-seq:
+ *    session.to_text -> String
+ *
+ * Shows everything in the Session object. This is for diagnostic purposes.
+ */
+static VALUE ossl_ssl_session_to_text(VALUE self)
+{
+	SSL_SESSION *ctx;
+	BIO *out;
+
+	GetSSLSession(self, ctx);
+
+	if (!(out = BIO_new(BIO_s_mem()))) {
+		ossl_raise(eSSLSession, "BIO_s_mem()");
+	}
+
+	if (!SSL_SESSION_print(out, ctx)) {
+		BIO_free(out);
+		ossl_raise(eSSLSession, "SSL_SESSION_print()");
+	}
+
+	return ossl_membio2str(out);
+}
+
+
+void Init_ossl_ssl_session(void)
+{
+#if 0
+    mOSSL = rb_define_module("OpenSSL");
+    mSSL = rb_define_module_under(mOSSL, "SSL");
+    eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError);
+#endif
+	cSSLSession = rb_define_class_under(mSSL, "Session", rb_cObject);
+	eSSLSession = rb_define_class_under(cSSLSession, "SessionError", eOSSLError);
+
+	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);
+
+	rb_define_method(cSSLSession, "time", ossl_ssl_session_get_time, 0);
+	rb_define_method(cSSLSession, "time=", ossl_ssl_session_set_time, 1);
+	rb_define_method(cSSLSession, "timeout", ossl_ssl_session_get_timeout, 0);
+	rb_define_method(cSSLSession, "timeout=", ossl_ssl_session_set_timeout, 1);
+	rb_define_method(cSSLSession, "id", ossl_ssl_session_get_id, 0);
+	rb_define_method(cSSLSession, "to_der", ossl_ssl_session_to_der, 0);
+	rb_define_method(cSSLSession, "to_pem", ossl_ssl_session_to_pem, 0);
+	rb_define_method(cSSLSession, "to_text", ossl_ssl_session_to_text, 0);
+}
diff --git a/ext/openssl/ossl_version.h b/ext/openssl/ossl_version.h
new file mode 100644
index 0000000..d5b9ac2
--- /dev/null
+++ b/ext/openssl/ossl_version.h
@@ -0,0 +1,15 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2001-2002  Michal Rokos <m.rokos at sh.cvut.cz>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#if !defined(_OSSL_VERSION_H_)
+#define _OSSL_VERSION_H_
+
+#define OSSL_VERSION "2.0.4"
+
+#endif /* _OSSL_VERSION_H_ */
diff --git a/ext/openssl/ossl_x509.c b/ext/openssl/ossl_x509.c
new file mode 100644
index 0000000..19ec274
--- /dev/null
+++ b/ext/openssl/ossl_x509.c
@@ -0,0 +1,186 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2001-2002  Michal Rokos <m.rokos at sh.cvut.cz>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#include "ossl.h"
+
+VALUE mX509;
+
+#define DefX509Const(x) rb_define_const(mX509, #x, INT2NUM(X509_##x))
+#define DefX509Default(x,i) \
+  rb_define_const(mX509, "DEFAULT_" #x, rb_str_new2(X509_get_default_##i()))
+
+ASN1_TIME *
+ossl_x509_time_adjust(ASN1_TIME *s, VALUE time)
+{
+    time_t sec;
+
+#if defined(HAVE_ASN1_TIME_ADJ)
+    int off_days;
+
+    ossl_time_split(time, &sec, &off_days);
+    return X509_time_adj_ex(s, off_days, 0, &sec);
+#else
+    sec = time_to_time_t(time);
+    return X509_time_adj(s, 0, &sec);
+#endif
+}
+
+void
+Init_ossl_x509(void)
+{
+#if 0
+    mOSSL = rb_define_module("OpenSSL");
+#endif
+
+    mX509 = rb_define_module_under(mOSSL, "X509");
+
+    Init_ossl_x509attr();
+    Init_ossl_x509cert();
+    Init_ossl_x509crl();
+    Init_ossl_x509ext();
+    Init_ossl_x509name();
+    Init_ossl_x509req();
+    Init_ossl_x509revoked();
+    Init_ossl_x509store();
+
+    DefX509Const(V_OK);
+    DefX509Const(V_ERR_UNABLE_TO_GET_ISSUER_CERT);
+    DefX509Const(V_ERR_UNABLE_TO_GET_CRL);
+    DefX509Const(V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE);
+    DefX509Const(V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE);
+    DefX509Const(V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY);
+    DefX509Const(V_ERR_CERT_SIGNATURE_FAILURE);
+    DefX509Const(V_ERR_CRL_SIGNATURE_FAILURE);
+    DefX509Const(V_ERR_CERT_NOT_YET_VALID);
+    DefX509Const(V_ERR_CERT_HAS_EXPIRED);
+    DefX509Const(V_ERR_CRL_NOT_YET_VALID);
+    DefX509Const(V_ERR_CRL_HAS_EXPIRED);
+    DefX509Const(V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD);
+    DefX509Const(V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD);
+    DefX509Const(V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD);
+    DefX509Const(V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD);
+    DefX509Const(V_ERR_OUT_OF_MEM);
+    DefX509Const(V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT);
+    DefX509Const(V_ERR_SELF_SIGNED_CERT_IN_CHAIN);
+    DefX509Const(V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY);
+    DefX509Const(V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE);
+    DefX509Const(V_ERR_CERT_CHAIN_TOO_LONG);
+    DefX509Const(V_ERR_CERT_REVOKED);
+    DefX509Const(V_ERR_INVALID_CA);
+    DefX509Const(V_ERR_PATH_LENGTH_EXCEEDED);
+    DefX509Const(V_ERR_INVALID_PURPOSE);
+    DefX509Const(V_ERR_CERT_UNTRUSTED);
+    DefX509Const(V_ERR_CERT_REJECTED);
+    DefX509Const(V_ERR_SUBJECT_ISSUER_MISMATCH);
+    DefX509Const(V_ERR_AKID_SKID_MISMATCH);
+    DefX509Const(V_ERR_AKID_ISSUER_SERIAL_MISMATCH);
+    DefX509Const(V_ERR_KEYUSAGE_NO_CERTSIGN);
+    DefX509Const(V_ERR_APPLICATION_VERIFICATION);
+
+    /* Set by Store#flags= and StoreContext#flags=. Enables CRL checking for the
+     * certificate chain leaf. */
+    DefX509Const(V_FLAG_CRL_CHECK);
+    /* Set by Store#flags= and StoreContext#flags=. Enables CRL checking for all
+     * certificates in the certificate chain */
+    DefX509Const(V_FLAG_CRL_CHECK_ALL);
+    /* Set by Store#flags= and StoreContext#flags=. Disables critical extension
+     * checking. */
+    DefX509Const(V_FLAG_IGNORE_CRITICAL);
+    /* Set by Store#flags= and StoreContext#flags=. Disables workarounds for
+     * broken certificates. */
+    DefX509Const(V_FLAG_X509_STRICT);
+    /* Set by Store#flags= and StoreContext#flags=. Enables proxy certificate
+     * verification. */
+    DefX509Const(V_FLAG_ALLOW_PROXY_CERTS);
+    /* Set by Store#flags= and StoreContext#flags=. Enables certificate policy
+     * constraints checking. */
+    DefX509Const(V_FLAG_POLICY_CHECK);
+    /* Set by Store#flags= and StoreContext#flags=.
+     * Implies V_FLAG_POLICY_CHECK */
+    DefX509Const(V_FLAG_EXPLICIT_POLICY);
+    /* Set by Store#flags= and StoreContext#flags=.
+     * Implies V_FLAG_POLICY_CHECK */
+    DefX509Const(V_FLAG_INHIBIT_ANY);
+    /* Set by Store#flags= and StoreContext#flags=.
+     * Implies V_FLAG_POLICY_CHECK */
+    DefX509Const(V_FLAG_INHIBIT_MAP);
+    /* Set by Store#flags= and StoreContext#flags=. */
+    DefX509Const(V_FLAG_NOTIFY_POLICY);
+#if defined(X509_V_FLAG_EXTENDED_CRL_SUPPORT)
+    /* Set by Store#flags= and StoreContext#flags=. Enables some additional
+     * features including support for indirect signed CRLs. */
+    DefX509Const(V_FLAG_EXTENDED_CRL_SUPPORT);
+#endif
+#if defined(X509_V_FLAG_USE_DELTAS)
+    /* Set by Store#flags= and StoreContext#flags=. Uses delta CRLs. If not
+     * specified, deltas are ignored. */
+    DefX509Const(V_FLAG_USE_DELTAS);
+#endif
+#if defined(X509_V_FLAG_CHECK_SS_SIGNATURE)
+    /* Set by Store#flags= and StoreContext#flags=. Enables checking of the
+     * signature of the root self-signed CA. */
+    DefX509Const(V_FLAG_CHECK_SS_SIGNATURE);
+#endif
+#if defined(X509_V_FLAG_TRUSTED_FIRST)
+    /* Set by Store#flags= and StoreContext#flags=. When constructing a
+     * certificate chain, search the Store first for the issuer certificate.
+     * Enabled by default in OpenSSL >= 1.1.0. */
+    DefX509Const(V_FLAG_TRUSTED_FIRST);
+#endif
+#if defined(X509_V_FLAG_NO_ALT_CHAINS)
+    /* Set by Store#flags= and StoreContext#flags=. Suppresses searching for
+     * a alternative chain. No effect in OpenSSL >= 1.1.0. */
+    DefX509Const(V_FLAG_NO_ALT_CHAINS);
+#endif
+#if defined(X509_V_FLAG_NO_CHECK_TIME)
+    /* Set by Store#flags= and StoreContext#flags=. Suppresses checking the
+     * validity period of certificates and CRLs. No effect when the current
+     * time is explicitly set by Store#time= or StoreContext#time=. */
+    DefX509Const(V_FLAG_NO_CHECK_TIME);
+#endif
+
+    /* Set by Store#purpose=. SSL/TLS client. */
+    DefX509Const(PURPOSE_SSL_CLIENT);
+    /* Set by Store#purpose=. SSL/TLS server. */
+    DefX509Const(PURPOSE_SSL_SERVER);
+    /* Set by Store#purpose=. Netscape SSL server. */
+    DefX509Const(PURPOSE_NS_SSL_SERVER);
+    /* Set by Store#purpose=. S/MIME signing. */
+    DefX509Const(PURPOSE_SMIME_SIGN);
+    /* Set by Store#purpose=. S/MIME encryption. */
+    DefX509Const(PURPOSE_SMIME_ENCRYPT);
+    /* Set by Store#purpose=. CRL signing */
+    DefX509Const(PURPOSE_CRL_SIGN);
+    /* Set by Store#purpose=. No checks. */
+    DefX509Const(PURPOSE_ANY);
+    /* Set by Store#purpose=. OCSP helper. */
+    DefX509Const(PURPOSE_OCSP_HELPER);
+#if defined(X509_PURPOSE_TIMESTAMP_SIGN)
+    /* Set by Store#purpose=. Time stamps signer. */
+    DefX509Const(PURPOSE_TIMESTAMP_SIGN);
+#endif
+
+    DefX509Const(TRUST_COMPAT);
+    DefX509Const(TRUST_SSL_CLIENT);
+    DefX509Const(TRUST_SSL_SERVER);
+    DefX509Const(TRUST_EMAIL);
+    DefX509Const(TRUST_OBJECT_SIGN);
+    DefX509Const(TRUST_OCSP_SIGN);
+    DefX509Const(TRUST_OCSP_REQUEST);
+#if defined(X509_TRUST_TSA)
+    DefX509Const(TRUST_TSA);
+#endif
+
+    DefX509Default(CERT_AREA, cert_area);
+    DefX509Default(CERT_DIR, cert_dir);
+    DefX509Default(CERT_FILE, cert_file);
+    DefX509Default(CERT_DIR_ENV, cert_dir_env);
+    DefX509Default(CERT_FILE_ENV, cert_file_env);
+    DefX509Default(PRIVATE_DIR, private_dir);
+}
diff --git a/ext/openssl/ossl_x509.h b/ext/openssl/ossl_x509.h
new file mode 100644
index 0000000..a60f7c3
--- /dev/null
+++ b/ext/openssl/ossl_x509.h
@@ -0,0 +1,122 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2001-2002  Michal Rokos <m.rokos at sh.cvut.cz>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#if !defined(_OSSL_X509_H_)
+#define _OSSL_X509_H_
+
+/*
+ * X509 main module
+ */
+extern VALUE mX509;
+
+/*
+ * Converts the VALUE into Integer and set it to the ASN1_TIME. This is a
+ * wrapper for X509_time_adj_ex() so passing NULL creates a new ASN1_TIME.
+ * Note that the caller must check the NULL return.
+ */
+ASN1_TIME *ossl_x509_time_adjust(ASN1_TIME *, VALUE);
+
+void Init_ossl_x509(void);
+
+/*
+ * X509Attr
+ */
+extern VALUE cX509Attr;
+extern VALUE eX509AttrError;
+
+VALUE ossl_x509attr_new(X509_ATTRIBUTE *);
+X509_ATTRIBUTE *GetX509AttrPtr(VALUE);
+void Init_ossl_x509attr(void);
+
+/*
+ * X509Cert
+ */
+extern VALUE cX509Cert;
+extern VALUE eX509CertError;
+
+VALUE ossl_x509_new(X509 *);
+VALUE ossl_x509_new_from_file(VALUE);
+X509 *GetX509CertPtr(VALUE);
+X509 *DupX509CertPtr(VALUE);
+void Init_ossl_x509cert(void);
+
+/*
+ * X509CRL
+ */
+extern VALUE cX509CRL;
+extern VALUE eX509CRLError;
+
+VALUE ossl_x509crl_new(X509_CRL *);
+X509_CRL *GetX509CRLPtr(VALUE);
+X509_CRL *DupX509CRLPtr(VALUE);
+void Init_ossl_x509crl(void);
+
+/*
+ * X509Extension
+ */
+extern VALUE cX509Ext;
+extern VALUE cX509ExtFactory;
+extern VALUE eX509ExtError;
+
+VALUE ossl_x509ext_new(X509_EXTENSION *);
+X509_EXTENSION *GetX509ExtPtr(VALUE);
+void Init_ossl_x509ext(void);
+
+/*
+ * X509Name
+ */
+extern VALUE cX509Name;
+extern VALUE eX509NameError;
+
+VALUE ossl_x509name_new(X509_NAME *);
+X509_NAME *GetX509NamePtr(VALUE);
+void Init_ossl_x509name(void);
+
+/*
+ * X509Request
+ */
+extern VALUE cX509Req;
+extern VALUE eX509ReqError;
+
+VALUE ossl_x509req_new(X509_REQ *);
+X509_REQ *GetX509ReqPtr(VALUE);
+X509_REQ *DupX509ReqPtr(VALUE);
+void Init_ossl_x509req(void);
+
+/*
+ * X509Revoked
+ */
+extern VALUE cX509Rev;
+extern VALUE eX509RevError;
+
+VALUE ossl_x509revoked_new(X509_REVOKED *);
+X509_REVOKED *DupX509RevokedPtr(VALUE);
+void Init_ossl_x509revoked(void);
+
+/*
+ * X509Store and X509StoreContext
+ */
+extern VALUE cX509Store;
+extern VALUE cX509StoreContext;
+extern VALUE eX509StoreError;
+
+VALUE ossl_x509store_new(X509_STORE *);
+X509_STORE *GetX509StorePtr(VALUE);
+X509_STORE *DupX509StorePtr(VALUE);
+
+X509_STORE_CTX *GetX509StCtxtPtr(VALUE);
+void Init_ossl_x509store(void);
+
+/*
+ * Calls the verify callback Proc (the first parameter) with given pre-verify
+ * result and the X509_STORE_CTX.
+ */
+int ossl_verify_cb_call(VALUE, int, X509_STORE_CTX *);
+
+#endif /* _OSSL_X509_H_ */
diff --git a/ext/openssl/ossl_x509attr.c b/ext/openssl/ossl_x509attr.c
new file mode 100644
index 0000000..ae0b347
--- /dev/null
+++ b/ext/openssl/ossl_x509attr.c
@@ -0,0 +1,328 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2001 Michal Rokos <m.rokos at sh.cvut.cz>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#include "ossl.h"
+
+#define NewX509Attr(klass) \
+    TypedData_Wrap_Struct((klass), &ossl_x509attr_type, 0)
+#define SetX509Attr(obj, attr) do { \
+    if (!(attr)) { \
+	ossl_raise(rb_eRuntimeError, "ATTR wasn't initialized!"); \
+    } \
+    RTYPEDDATA_DATA(obj) = (attr); \
+} while (0)
+#define GetX509Attr(obj, attr) do { \
+    TypedData_Get_Struct((obj), X509_ATTRIBUTE, &ossl_x509attr_type, (attr)); \
+    if (!(attr)) { \
+	ossl_raise(rb_eRuntimeError, "ATTR wasn't initialized!"); \
+    } \
+} while (0)
+#define SafeGetX509Attr(obj, attr) do { \
+    OSSL_Check_Kind((obj), cX509Attr); \
+    GetX509Attr((obj), (attr)); \
+} while (0)
+
+/*
+ * Classes
+ */
+VALUE cX509Attr;
+VALUE eX509AttrError;
+
+static void
+ossl_x509attr_free(void *ptr)
+{
+    X509_ATTRIBUTE_free(ptr);
+}
+
+static const rb_data_type_t ossl_x509attr_type = {
+    "OpenSSL/X509/ATTRIBUTE",
+    {
+	0, ossl_x509attr_free,
+    },
+    0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
+};
+
+/*
+ * Public
+ */
+VALUE
+ossl_x509attr_new(X509_ATTRIBUTE *attr)
+{
+    X509_ATTRIBUTE *new;
+    VALUE obj;
+
+    obj = NewX509Attr(cX509Attr);
+    if (!attr) {
+	new = X509_ATTRIBUTE_new();
+    } else {
+	new = X509_ATTRIBUTE_dup(attr);
+    }
+    if (!new) {
+	ossl_raise(eX509AttrError, NULL);
+    }
+    SetX509Attr(obj, new);
+
+    return obj;
+}
+
+X509_ATTRIBUTE *
+GetX509AttrPtr(VALUE obj)
+{
+    X509_ATTRIBUTE *attr;
+
+    SafeGetX509Attr(obj, attr);
+
+    return attr;
+}
+
+/*
+ * Private
+ */
+static VALUE
+ossl_x509attr_alloc(VALUE klass)
+{
+    X509_ATTRIBUTE *attr;
+    VALUE obj;
+
+    obj = NewX509Attr(klass);
+    if (!(attr = X509_ATTRIBUTE_new()))
+	ossl_raise(eX509AttrError, NULL);
+    SetX509Attr(obj, attr);
+
+    return obj;
+}
+
+/*
+ * call-seq:
+ *    Attribute.new(oid [, value]) => attr
+ */
+static VALUE
+ossl_x509attr_initialize(int argc, VALUE *argv, VALUE self)
+{
+    VALUE oid, value;
+    X509_ATTRIBUTE *attr, *x;
+    const unsigned char *p;
+
+    GetX509Attr(self, attr);
+    if(rb_scan_args(argc, argv, "11", &oid, &value) == 1){
+	oid = ossl_to_der_if_possible(oid);
+	StringValue(oid);
+	p = (unsigned char *)RSTRING_PTR(oid);
+	x = d2i_X509_ATTRIBUTE(&attr, &p, RSTRING_LEN(oid));
+	DATA_PTR(self) = attr;
+	if(!x){
+	    ossl_raise(eX509AttrError, NULL);
+	}
+	return self;
+    }
+    rb_funcall(self, rb_intern("oid="), 1, oid);
+    rb_funcall(self, rb_intern("value="), 1, value);
+
+    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
+ */
+static VALUE
+ossl_x509attr_set_oid(VALUE self, VALUE oid)
+{
+    X509_ATTRIBUTE *attr;
+    ASN1_OBJECT *obj;
+    char *s;
+
+    GetX509Attr(self, attr);
+    s = StringValueCStr(oid);
+    obj = OBJ_txt2obj(s, 0);
+    if(!obj) ossl_raise(eX509AttrError, NULL);
+    if (!X509_ATTRIBUTE_set1_object(attr, obj)) {
+	ASN1_OBJECT_free(obj);
+	ossl_raise(eX509AttrError, "X509_ATTRIBUTE_set1_object");
+    }
+    ASN1_OBJECT_free(obj);
+
+    return oid;
+}
+
+/*
+ * call-seq:
+ *    attr.oid => string
+ */
+static VALUE
+ossl_x509attr_get_oid(VALUE self)
+{
+    X509_ATTRIBUTE *attr;
+    ASN1_OBJECT *oid;
+    BIO *out;
+    VALUE ret;
+    int nid;
+
+    GetX509Attr(self, attr);
+    oid = X509_ATTRIBUTE_get0_object(attr);
+    if ((nid = OBJ_obj2nid(oid)) != NID_undef)
+	ret = rb_str_new2(OBJ_nid2sn(nid));
+    else{
+	if (!(out = BIO_new(BIO_s_mem())))
+	    ossl_raise(eX509AttrError, NULL);
+	i2a_ASN1_OBJECT(out, oid);
+	ret = ossl_membio2str(out);
+    }
+
+    return ret;
+}
+
+/*
+ * call-seq:
+ *    attr.value = asn1 => asn1
+ */
+static VALUE
+ossl_x509attr_set_value(VALUE self, VALUE value)
+{
+    X509_ATTRIBUTE *attr;
+    VALUE asn1_value;
+    int i, asn1_tag;
+
+    OSSL_Check_Kind(value, cASN1Data);
+    asn1_tag = NUM2INT(rb_attr_get(value, rb_intern("@tag")));
+    asn1_value = rb_attr_get(value, rb_intern("@value"));
+    if (asn1_tag != V_ASN1_SET)
+	ossl_raise(eASN1Error, "argument must be ASN1::Set");
+    if (!RB_TYPE_P(asn1_value, T_ARRAY))
+	ossl_raise(eASN1Error, "ASN1::Set has non-array value");
+
+    GetX509Attr(self, attr);
+    if (X509_ATTRIBUTE_count(attr)) { /* populated, reset first */
+	ASN1_OBJECT *obj = X509_ATTRIBUTE_get0_object(attr);
+	X509_ATTRIBUTE *new_attr = X509_ATTRIBUTE_create_by_OBJ(NULL, obj, 0, NULL, -1);
+	if (!new_attr)
+	    ossl_raise(eX509AttrError, NULL);
+	SetX509Attr(self, new_attr);
+	X509_ATTRIBUTE_free(attr);
+	attr = new_attr;
+    }
+
+    for (i = 0; i < RARRAY_LEN(asn1_value); i++) {
+	ASN1_TYPE *a1type = ossl_asn1_get_asn1type(RARRAY_AREF(asn1_value, i));
+	if (!X509_ATTRIBUTE_set1_data(attr, ASN1_TYPE_get(a1type),
+				      a1type->value.ptr, -1)) {
+	    ASN1_TYPE_free(a1type);
+	    ossl_raise(eX509AttrError, NULL);
+	}
+	ASN1_TYPE_free(a1type);
+    }
+
+    return value;
+}
+
+/*
+ * call-seq:
+ *    attr.value => asn1
+ */
+static VALUE
+ossl_x509attr_get_value(VALUE self)
+{
+    X509_ATTRIBUTE *attr;
+    STACK_OF(ASN1_TYPE) *sk;
+    VALUE str;
+    int i, count, len;
+    unsigned char *p;
+
+    GetX509Attr(self, attr);
+    /* there is no X509_ATTRIBUTE_get0_set() :( */
+    if (!(sk = sk_ASN1_TYPE_new_null()))
+	ossl_raise(eX509AttrError, "sk_new");
+
+    count = X509_ATTRIBUTE_count(attr);
+    for (i = 0; i < count; i++)
+	sk_ASN1_TYPE_push(sk, X509_ATTRIBUTE_get0_type(attr, i));
+
+    if ((len = i2d_ASN1_SET_ANY(sk, NULL)) <= 0) {
+	sk_ASN1_TYPE_free(sk);
+	ossl_raise(eX509AttrError, NULL);
+    }
+    str = rb_str_new(0, len);
+    p = (unsigned char *)RSTRING_PTR(str);
+    if (i2d_ASN1_SET_ANY(sk, &p) <= 0) {
+	sk_ASN1_TYPE_free(sk);
+	ossl_raise(eX509AttrError, NULL);
+    }
+    ossl_str_adjust(str, p);
+    sk_ASN1_TYPE_free(sk);
+
+    return rb_funcall(mASN1, rb_intern("decode"), 1, str);
+}
+
+/*
+ * call-seq:
+ *    attr.to_der => string
+ */
+static VALUE
+ossl_x509attr_to_der(VALUE self)
+{
+    X509_ATTRIBUTE *attr;
+    VALUE str;
+    int len;
+    unsigned char *p;
+
+    GetX509Attr(self, attr);
+    if((len = i2d_X509_ATTRIBUTE(attr, NULL)) <= 0)
+	ossl_raise(eX509AttrError, NULL);
+    str = rb_str_new(0, len);
+    p = (unsigned char *)RSTRING_PTR(str);
+    if(i2d_X509_ATTRIBUTE(attr, &p) <= 0)
+	ossl_raise(eX509AttrError, NULL);
+    ossl_str_adjust(str, p);
+
+    return str;
+}
+
+/*
+ * X509_ATTRIBUTE init
+ */
+void
+Init_ossl_x509attr(void)
+{
+#if 0
+    mOSSL = rb_define_module("OpenSSL");
+    eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError);
+    mX509 = rb_define_module_under(mOSSL, "X509");
+#endif
+
+    eX509AttrError = rb_define_class_under(mX509, "AttributeError", eOSSLError);
+
+    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);
+    rb_define_method(cX509Attr, "value", ossl_x509attr_get_value, 0);
+    rb_define_method(cX509Attr, "to_der", ossl_x509attr_to_der, 0);
+}
diff --git a/ext/openssl/ossl_x509cert.c b/ext/openssl/ossl_x509cert.c
new file mode 100644
index 0000000..cecc3ca
--- /dev/null
+++ b/ext/openssl/ossl_x509cert.c
@@ -0,0 +1,860 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2001-2002  Michal Rokos <m.rokos at sh.cvut.cz>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#include "ossl.h"
+
+#define NewX509(klass) \
+    TypedData_Wrap_Struct((klass), &ossl_x509_type, 0)
+#define SetX509(obj, x509) do { \
+    if (!(x509)) { \
+	ossl_raise(rb_eRuntimeError, "CERT wasn't initialized!"); \
+    } \
+    RTYPEDDATA_DATA(obj) = (x509); \
+} while (0)
+#define GetX509(obj, x509) do { \
+    TypedData_Get_Struct((obj), X509, &ossl_x509_type, (x509)); \
+    if (!(x509)) { \
+	ossl_raise(rb_eRuntimeError, "CERT wasn't initialized!"); \
+    } \
+} while (0)
+#define SafeGetX509(obj, x509) do { \
+    OSSL_Check_Kind((obj), cX509Cert); \
+    GetX509((obj), (x509)); \
+} while (0)
+
+/*
+ * Classes
+ */
+VALUE cX509Cert;
+VALUE eX509CertError;
+
+static void
+ossl_x509_free(void *ptr)
+{
+    X509_free(ptr);
+}
+
+static const rb_data_type_t ossl_x509_type = {
+    "OpenSSL/X509",
+    {
+	0, ossl_x509_free,
+    },
+    0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
+};
+
+/*
+ * Public
+ */
+VALUE
+ossl_x509_new(X509 *x509)
+{
+    X509 *new;
+    VALUE obj;
+
+    obj = NewX509(cX509Cert);
+    if (!x509) {
+	new = X509_new();
+    } else {
+	new = X509_dup(x509);
+    }
+    if (!new) {
+	ossl_raise(eX509CertError, NULL);
+    }
+    SetX509(obj, new);
+
+    return obj;
+}
+
+VALUE
+ossl_x509_new_from_file(VALUE filename)
+{
+    X509 *x509;
+    FILE *fp;
+    VALUE obj;
+
+    rb_check_safe_obj(filename);
+    obj = NewX509(cX509Cert);
+    if (!(fp = fopen(StringValueCStr(filename), "r"))) {
+	ossl_raise(eX509CertError, "%s", strerror(errno));
+    }
+    rb_fd_fix_cloexec(fileno(fp));
+    x509 = PEM_read_X509(fp, NULL, NULL, NULL);
+    /*
+     * prepare for DER...
+#if !defined(OPENSSL_NO_FP_API)
+    if (!x509) {
+    	(void)ERR_get_error();
+	rewind(fp);
+
+	x509 = d2i_X509_fp(fp, NULL);
+    }
+#endif
+    */
+    fclose(fp);
+    if (!x509) {
+	ossl_raise(eX509CertError, NULL);
+    }
+    SetX509(obj, x509);
+
+    return obj;
+}
+
+X509 *
+GetX509CertPtr(VALUE obj)
+{
+    X509 *x509;
+
+    SafeGetX509(obj, x509);
+
+    return x509;
+}
+
+X509 *
+DupX509CertPtr(VALUE obj)
+{
+    X509 *x509;
+
+    SafeGetX509(obj, x509);
+
+    X509_up_ref(x509);
+
+    return x509;
+}
+
+/*
+ * Private
+ */
+static VALUE
+ossl_x509_alloc(VALUE klass)
+{
+    X509 *x509;
+    VALUE obj;
+
+    obj = NewX509(klass);
+    x509 = X509_new();
+    if (!x509) ossl_raise(eX509CertError, NULL);
+    SetX509(obj, x509);
+
+    return obj;
+}
+
+/*
+ * call-seq:
+ *    Certificate.new => cert
+ *    Certificate.new(string) => cert
+ */
+static VALUE
+ossl_x509_initialize(int argc, VALUE *argv, VALUE self)
+{
+    BIO *in;
+    X509 *x509, *x = DATA_PTR(self);
+    VALUE arg;
+
+    if (rb_scan_args(argc, argv, "01", &arg) == 0) {
+	/* create just empty X509Cert */
+	return self;
+    }
+    arg = ossl_to_der_if_possible(arg);
+    in = ossl_obj2bio(arg);
+    x509 = PEM_read_bio_X509(in, &x, NULL, NULL);
+    DATA_PTR(self) = x;
+    if (!x509) {
+	OSSL_BIO_reset(in);
+	x509 = d2i_X509_bio(in, &x);
+	DATA_PTR(self) = x;
+    }
+    BIO_free(in);
+    if (!x509) ossl_raise(eX509CertError, NULL);
+
+    return self;
+}
+
+static VALUE
+ossl_x509_copy(VALUE self, VALUE other)
+{
+    X509 *a, *b, *x509;
+
+    rb_check_frozen(self);
+    if (self == other) return self;
+
+    GetX509(self, a);
+    SafeGetX509(other, b);
+
+    x509 = X509_dup(b);
+    if (!x509) ossl_raise(eX509CertError, NULL);
+
+    DATA_PTR(self) = x509;
+    X509_free(a);
+
+    return self;
+}
+
+/*
+ * call-seq:
+ *    cert.to_der => string
+ */
+static VALUE
+ossl_x509_to_der(VALUE self)
+{
+    X509 *x509;
+    VALUE str;
+    long len;
+    unsigned char *p;
+
+    GetX509(self, x509);
+    if ((len = i2d_X509(x509, NULL)) <= 0)
+	ossl_raise(eX509CertError, NULL);
+    str = rb_str_new(0, len);
+    p = (unsigned char *)RSTRING_PTR(str);
+    if (i2d_X509(x509, &p) <= 0)
+	ossl_raise(eX509CertError, NULL);
+    ossl_str_adjust(str, p);
+
+    return str;
+}
+
+/*
+ * call-seq:
+ *    cert.to_pem => string
+ */
+static VALUE
+ossl_x509_to_pem(VALUE self)
+{
+    X509 *x509;
+    BIO *out;
+    VALUE str;
+
+    GetX509(self, x509);
+    out = BIO_new(BIO_s_mem());
+    if (!out) ossl_raise(eX509CertError, NULL);
+
+    if (!PEM_write_bio_X509(out, x509)) {
+	BIO_free(out);
+	ossl_raise(eX509CertError, NULL);
+    }
+    str = ossl_membio2str(out);
+
+    return str;
+}
+
+/*
+ * call-seq:
+ *    cert.to_text => string
+ */
+static VALUE
+ossl_x509_to_text(VALUE self)
+{
+    X509 *x509;
+    BIO *out;
+    VALUE str;
+
+    GetX509(self, x509);
+
+    out = BIO_new(BIO_s_mem());
+    if (!out) ossl_raise(eX509CertError, NULL);
+
+    if (!X509_print(out, x509)) {
+	BIO_free(out);
+	ossl_raise(eX509CertError, NULL);
+    }
+    str = ossl_membio2str(out);
+
+    return str;
+}
+
+#if 0
+/*
+ * Makes from X509 X509_REQuest
+ */
+static VALUE
+ossl_x509_to_req(VALUE self)
+{
+    X509 *x509;
+    X509_REQ *req;
+    VALUE obj;
+
+    GetX509(self, x509);
+    if (!(req = X509_to_X509_REQ(x509, NULL, EVP_md5()))) {
+	ossl_raise(eX509CertError, NULL);
+    }
+    obj = ossl_x509req_new(req);
+    X509_REQ_free(req);
+
+    return obj;
+}
+#endif
+
+/*
+ * call-seq:
+ *    cert.version => integer
+ */
+static VALUE
+ossl_x509_get_version(VALUE self)
+{
+    X509 *x509;
+
+    GetX509(self, x509);
+
+    return LONG2NUM(X509_get_version(x509));
+}
+
+/*
+ * call-seq:
+ *    cert.version = integer => integer
+ */
+static VALUE
+ossl_x509_set_version(VALUE self, VALUE version)
+{
+    X509 *x509;
+    long ver;
+
+    if ((ver = NUM2LONG(version)) < 0) {
+	ossl_raise(eX509CertError, "version must be >= 0!");
+    }
+    GetX509(self, x509);
+    if (!X509_set_version(x509, ver)) {
+	ossl_raise(eX509CertError, NULL);
+    }
+
+    return version;
+}
+
+/*
+ * call-seq:
+ *    cert.serial => integer
+ */
+static VALUE
+ossl_x509_get_serial(VALUE self)
+{
+    X509 *x509;
+
+    GetX509(self, x509);
+
+    return asn1integer_to_num(X509_get_serialNumber(x509));
+}
+
+/*
+ * call-seq:
+ *    cert.serial = integer => integer
+ */
+static VALUE
+ossl_x509_set_serial(VALUE self, VALUE num)
+{
+    X509 *x509;
+
+    GetX509(self, x509);
+    X509_set_serialNumber(x509, num_to_asn1integer(num, X509_get_serialNumber(x509)));
+
+    return num;
+}
+
+/*
+ * call-seq:
+ *    cert.signature_algorithm => string
+ */
+static VALUE
+ossl_x509_get_signature_algorithm(VALUE self)
+{
+    X509 *x509;
+    BIO *out;
+    VALUE str;
+
+    GetX509(self, x509);
+    out = BIO_new(BIO_s_mem());
+    if (!out) ossl_raise(eX509CertError, NULL);
+
+    if (!i2a_ASN1_OBJECT(out, X509_get0_tbs_sigalg(x509)->algorithm)) {
+	BIO_free(out);
+	ossl_raise(eX509CertError, NULL);
+    }
+    str = ossl_membio2str(out);
+
+    return str;
+}
+
+/*
+ * call-seq:
+ *    cert.subject => name
+ */
+static VALUE
+ossl_x509_get_subject(VALUE self)
+{
+    X509 *x509;
+    X509_NAME *name;
+
+    GetX509(self, x509);
+    if (!(name = X509_get_subject_name(x509))) { /* NO DUP - don't free! */
+	ossl_raise(eX509CertError, NULL);
+    }
+
+    return ossl_x509name_new(name);
+}
+
+/*
+ * call-seq:
+ *    cert.subject = name => name
+ */
+static VALUE
+ossl_x509_set_subject(VALUE self, VALUE subject)
+{
+    X509 *x509;
+
+    GetX509(self, x509);
+    if (!X509_set_subject_name(x509, GetX509NamePtr(subject))) { /* DUPs name */
+	ossl_raise(eX509CertError, NULL);
+    }
+
+    return subject;
+}
+
+/*
+ * call-seq:
+ *    cert.issuer => name
+ */
+static VALUE
+ossl_x509_get_issuer(VALUE self)
+{
+    X509 *x509;
+    X509_NAME *name;
+
+    GetX509(self, x509);
+    if(!(name = X509_get_issuer_name(x509))) { /* NO DUP - don't free! */
+	ossl_raise(eX509CertError, NULL);
+    }
+
+    return ossl_x509name_new(name);
+}
+
+/*
+ * call-seq:
+ *    cert.issuer = name => name
+ */
+static VALUE
+ossl_x509_set_issuer(VALUE self, VALUE issuer)
+{
+    X509 *x509;
+
+    GetX509(self, x509);
+    if (!X509_set_issuer_name(x509, GetX509NamePtr(issuer))) { /* DUPs name */
+	ossl_raise(eX509CertError, NULL);
+    }
+
+    return issuer;
+}
+
+/*
+ * call-seq:
+ *    cert.not_before => time
+ */
+static VALUE
+ossl_x509_get_not_before(VALUE self)
+{
+    X509 *x509;
+    const ASN1_TIME *asn1time;
+
+    GetX509(self, x509);
+    if (!(asn1time = X509_get0_notBefore(x509))) {
+	ossl_raise(eX509CertError, NULL);
+    }
+
+    return asn1time_to_time(asn1time);
+}
+
+/*
+ * call-seq:
+ *    cert.not_before = time => time
+ */
+static VALUE
+ossl_x509_set_not_before(VALUE self, VALUE time)
+{
+    X509 *x509;
+    ASN1_TIME *asn1time;
+
+    GetX509(self, x509);
+    asn1time = ossl_x509_time_adjust(NULL, time);
+    if (!X509_set_notBefore(x509, asn1time)) {
+	ASN1_TIME_free(asn1time);
+	ossl_raise(eX509CertError, "X509_set_notBefore");
+    }
+    ASN1_TIME_free(asn1time);
+
+    return time;
+}
+
+/*
+ * call-seq:
+ *    cert.not_after => time
+ */
+static VALUE
+ossl_x509_get_not_after(VALUE self)
+{
+    X509 *x509;
+    const ASN1_TIME *asn1time;
+
+    GetX509(self, x509);
+    if (!(asn1time = X509_get0_notAfter(x509))) {
+	ossl_raise(eX509CertError, NULL);
+    }
+
+    return asn1time_to_time(asn1time);
+}
+
+/*
+ * call-seq:
+ *    cert.not_after = time => time
+ */
+static VALUE
+ossl_x509_set_not_after(VALUE self, VALUE time)
+{
+    X509 *x509;
+    ASN1_TIME *asn1time;
+
+    GetX509(self, x509);
+    asn1time = ossl_x509_time_adjust(NULL, time);
+    if (!X509_set_notAfter(x509, asn1time)) {
+	ASN1_TIME_free(asn1time);
+	ossl_raise(eX509CertError, "X509_set_notAfter");
+    }
+    ASN1_TIME_free(asn1time);
+
+    return time;
+}
+
+/*
+ * call-seq:
+ *    cert.public_key => key
+ */
+static VALUE
+ossl_x509_get_public_key(VALUE self)
+{
+    X509 *x509;
+    EVP_PKEY *pkey;
+
+    GetX509(self, x509);
+    if (!(pkey = X509_get_pubkey(x509))) { /* adds an reference */
+	ossl_raise(eX509CertError, NULL);
+    }
+
+    return ossl_pkey_new(pkey); /* NO DUP - OK */
+}
+
+/*
+ * call-seq:
+ *    cert.public_key = key => key
+ */
+static VALUE
+ossl_x509_set_public_key(VALUE self, VALUE key)
+{
+    X509 *x509;
+
+    GetX509(self, x509);
+    if (!X509_set_pubkey(x509, GetPKeyPtr(key))) { /* DUPs pkey */
+	ossl_raise(eX509CertError, NULL);
+    }
+
+    return key;
+}
+
+/*
+ * call-seq:
+ *    cert.sign(key, digest) => self
+ */
+static VALUE
+ossl_x509_sign(VALUE self, VALUE key, VALUE digest)
+{
+    X509 *x509;
+    EVP_PKEY *pkey;
+    const EVP_MD *md;
+
+    pkey = GetPrivPKeyPtr(key); /* NO NEED TO DUP */
+    md = GetDigestPtr(digest);
+    GetX509(self, x509);
+    if (!X509_sign(x509, pkey, md)) {
+	ossl_raise(eX509CertError, NULL);
+    }
+
+    return self;
+}
+
+/*
+ * call-seq:
+ *    cert.verify(key) => true | false
+ *
+ * Checks that cert signature is made with PRIVversion of this PUBLIC 'key'
+ */
+static VALUE
+ossl_x509_verify(VALUE self, VALUE key)
+{
+    X509 *x509;
+    EVP_PKEY *pkey;
+
+    pkey = GetPKeyPtr(key); /* NO NEED TO DUP */
+    GetX509(self, x509);
+
+    switch (X509_verify(x509, pkey)) {
+      case 1:
+	return Qtrue;
+      case 0:
+	ossl_clear_error();
+	return Qfalse;
+      default:
+	ossl_raise(eX509CertError, NULL);
+    }
+}
+
+/*
+ * call-seq:
+ *    cert.check_private_key(key)
+ *
+ * Checks if 'key' is PRIV key for this cert
+ */
+static VALUE
+ossl_x509_check_private_key(VALUE self, VALUE key)
+{
+    X509 *x509;
+    EVP_PKEY *pkey;
+
+    /* not needed private key, but should be */
+    pkey = GetPrivPKeyPtr(key); /* NO NEED TO DUP */
+    GetX509(self, x509);
+    if (!X509_check_private_key(x509, pkey)) {
+	ossl_clear_error();
+	return Qfalse;
+    }
+
+    return Qtrue;
+}
+
+/*
+ * call-seq:
+ *    cert.extensions => [extension...]
+ */
+static VALUE
+ossl_x509_get_extensions(VALUE self)
+{
+    X509 *x509;
+    int count, i;
+    X509_EXTENSION *ext;
+    VALUE ary;
+
+    GetX509(self, x509);
+    count = X509_get_ext_count(x509);
+    if (count < 0) {
+	return rb_ary_new();
+    }
+    ary = rb_ary_new2(count);
+    for (i=0; i<count; i++) {
+	ext = X509_get_ext(x509, i); /* NO DUP - don't free! */
+	rb_ary_push(ary, ossl_x509ext_new(ext));
+    }
+
+    return ary;
+}
+
+/*
+ * call-seq:
+ *    cert.extensions = [ext...] => [ext...]
+ */
+static VALUE
+ossl_x509_set_extensions(VALUE self, VALUE ary)
+{
+    X509 *x509;
+    X509_EXTENSION *ext;
+    long i;
+
+    Check_Type(ary, T_ARRAY);
+    /* All ary's members should be X509Extension */
+    for (i=0; i<RARRAY_LEN(ary); i++) {
+	OSSL_Check_Kind(RARRAY_AREF(ary, i), cX509Ext);
+    }
+    GetX509(self, x509);
+    while ((ext = X509_delete_ext(x509, 0)))
+	X509_EXTENSION_free(ext);
+    for (i=0; i<RARRAY_LEN(ary); i++) {
+	ext = GetX509ExtPtr(RARRAY_AREF(ary, i));
+	if (!X509_add_ext(x509, ext, -1)) { /* DUPs ext */
+	    ossl_raise(eX509CertError, NULL);
+	}
+    }
+
+    return ary;
+}
+
+/*
+ * call-seq:
+ *    cert.add_extension(extension) => extension
+ */
+static VALUE
+ossl_x509_add_extension(VALUE self, VALUE extension)
+{
+    X509 *x509;
+    X509_EXTENSION *ext;
+
+    GetX509(self, x509);
+    ext = GetX509ExtPtr(extension);
+    if (!X509_add_ext(x509, ext, -1)) { /* DUPs ext - FREE it */
+	ossl_raise(eX509CertError, NULL);
+    }
+
+    return extension;
+}
+
+static VALUE
+ossl_x509_inspect(VALUE self)
+{
+    return rb_sprintf("#<%"PRIsVALUE": subject=%+"PRIsVALUE", "
+		      "issuer=%+"PRIsVALUE", serial=%+"PRIsVALUE", "
+		      "not_before=%+"PRIsVALUE", not_after=%+"PRIsVALUE">",
+		      rb_obj_class(self),
+		      ossl_x509_get_subject(self),
+		      ossl_x509_get_issuer(self),
+		      ossl_x509_get_serial(self),
+		      ossl_x509_get_not_before(self),
+		      ossl_x509_get_not_after(self));
+}
+
+/*
+ * INIT
+ */
+void
+Init_ossl_x509cert(void)
+{
+#if 0
+    mOSSL = rb_define_module("OpenSSL");
+    eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError);
+    mX509 = rb_define_module_under(mOSSL, "X509");
+#endif
+
+    eX509CertError = rb_define_class_under(mX509, "CertificateError", eOSSLError);
+
+    /* Document-class: OpenSSL::X509::Certificate
+     *
+     * Implementation of an X.509 certificate as specified in RFC 5280.
+     * Provides access to a certificate's attributes and allows certificates
+     * to be read from a string, but also supports the creation of new
+     * certificates from scratch.
+     *
+     * === Reading a certificate from a file
+     *
+     * Certificate is capable of handling DER-encoded certificates and
+     * certificates encoded in OpenSSL's PEM format.
+     *
+     *   raw = File.read "cert.cer" # DER- or PEM-encoded
+     *   certificate = OpenSSL::X509::Certificate.new raw
+     *
+     * === Saving a certificate to a file
+     *
+     * A certificate may be encoded in DER format
+     *
+     *   cert = ...
+     *   File.open("cert.cer", "wb") { |f| f.print cert.to_der }
+     *
+     * or in PEM format
+     *
+     *   cert = ...
+     *   File.open("cert.pem", "wb") { |f| f.print cert.to_pem }
+     *
+     * X.509 certificates are associated with a private/public key pair,
+     * typically a RSA, DSA or ECC key (see also OpenSSL::PKey::RSA,
+     * OpenSSL::PKey::DSA and OpenSSL::PKey::EC), the public key itself is
+     * stored within the certificate and can be accessed in form of an
+     * OpenSSL::PKey. Certificates are typically used to be able to associate
+     * some form of identity with a key pair, for example web servers serving
+     * pages over HTTPs use certificates to authenticate themselves to the user.
+     *
+     * The public key infrastructure (PKI) model relies on trusted certificate
+     * authorities ("root CAs") that issue these certificates, so that end
+     * users need to base their trust just on a selected few authorities
+     * that themselves again vouch for subordinate CAs issuing their
+     * certificates to end users.
+     *
+     * The OpenSSL::X509 module provides the tools to set up an independent
+     * PKI, similar to scenarios where the 'openssl' command line tool is
+     * used for issuing certificates in a private PKI.
+     *
+     * === Creating a root CA certificate and an end-entity certificate
+     *
+     * First, we need to create a "self-signed" root certificate. To do so,
+     * we need to generate a key first. Please note that the choice of "1"
+     * as a serial number is considered a security flaw for real certificates.
+     * Secure choices are integers in the two-digit byte range and ideally
+     * not sequential but secure random numbers, steps omitted here to keep
+     * the example concise.
+     *
+     *   root_key = OpenSSL::PKey::RSA.new 2048 # the CA's public/private key
+     *   root_ca = OpenSSL::X509::Certificate.new
+     *   root_ca.version = 2 # cf. RFC 5280 - to make it a "v3" certificate
+     *   root_ca.serial = 1
+     *   root_ca.subject = OpenSSL::X509::Name.parse "/DC=org/DC=ruby-lang/CN=Ruby CA"
+     *   root_ca.issuer = root_ca.subject # root CA's are "self-signed"
+     *   root_ca.public_key = root_key.public_key
+     *   root_ca.not_before = Time.now
+     *   root_ca.not_after = root_ca.not_before + 2 * 365 * 24 * 60 * 60 # 2 years validity
+     *   ef = OpenSSL::X509::ExtensionFactory.new
+     *   ef.subject_certificate = root_ca
+     *   ef.issuer_certificate = root_ca
+     *   root_ca.add_extension(ef.create_extension("basicConstraints","CA:TRUE",true))
+     *   root_ca.add_extension(ef.create_extension("keyUsage","keyCertSign, cRLSign", true))
+     *   root_ca.add_extension(ef.create_extension("subjectKeyIdentifier","hash",false))
+     *   root_ca.add_extension(ef.create_extension("authorityKeyIdentifier","keyid:always",false))
+     *   root_ca.sign(root_key, OpenSSL::Digest::SHA256.new)
+     *
+     * The next step is to create the end-entity certificate using the root CA
+     * certificate.
+     *
+     *   key = OpenSSL::PKey::RSA.new 2048
+     *   cert = OpenSSL::X509::Certificate.new
+     *   cert.version = 2
+     *   cert.serial = 2
+     *   cert.subject = OpenSSL::X509::Name.parse "/DC=org/DC=ruby-lang/CN=Ruby certificate"
+     *   cert.issuer = root_ca.subject # root CA is the issuer
+     *   cert.public_key = key.public_key
+     *   cert.not_before = Time.now
+     *   cert.not_after = cert.not_before + 1 * 365 * 24 * 60 * 60 # 1 years validity
+     *   ef = OpenSSL::X509::ExtensionFactory.new
+     *   ef.subject_certificate = cert
+     *   ef.issuer_certificate = root_ca
+     *   cert.add_extension(ef.create_extension("keyUsage","digitalSignature", true))
+     *   cert.add_extension(ef.create_extension("subjectKeyIdentifier","hash",false))
+     *   cert.sign(root_key, OpenSSL::Digest::SHA256.new)
+     *
+     */
+    cX509Cert = rb_define_class_under(mX509, "Certificate", rb_cObject);
+
+    rb_define_alloc_func(cX509Cert, ossl_x509_alloc);
+    rb_define_method(cX509Cert, "initialize", ossl_x509_initialize, -1);
+    rb_define_copy_func(cX509Cert, ossl_x509_copy);
+
+    rb_define_method(cX509Cert, "to_der", ossl_x509_to_der, 0);
+    rb_define_method(cX509Cert, "to_pem", ossl_x509_to_pem, 0);
+    rb_define_alias(cX509Cert, "to_s", "to_pem");
+    rb_define_method(cX509Cert, "to_text", ossl_x509_to_text, 0);
+    rb_define_method(cX509Cert, "version", ossl_x509_get_version, 0);
+    rb_define_method(cX509Cert, "version=", ossl_x509_set_version, 1);
+    rb_define_method(cX509Cert, "signature_algorithm", ossl_x509_get_signature_algorithm, 0);
+    rb_define_method(cX509Cert, "serial", ossl_x509_get_serial, 0);
+    rb_define_method(cX509Cert, "serial=", ossl_x509_set_serial, 1);
+    rb_define_method(cX509Cert, "subject", ossl_x509_get_subject, 0);
+    rb_define_method(cX509Cert, "subject=", ossl_x509_set_subject, 1);
+    rb_define_method(cX509Cert, "issuer", ossl_x509_get_issuer, 0);
+    rb_define_method(cX509Cert, "issuer=", ossl_x509_set_issuer, 1);
+    rb_define_method(cX509Cert, "not_before", ossl_x509_get_not_before, 0);
+    rb_define_method(cX509Cert, "not_before=", ossl_x509_set_not_before, 1);
+    rb_define_method(cX509Cert, "not_after", ossl_x509_get_not_after, 0);
+    rb_define_method(cX509Cert, "not_after=", ossl_x509_set_not_after, 1);
+    rb_define_method(cX509Cert, "public_key", ossl_x509_get_public_key, 0);
+    rb_define_method(cX509Cert, "public_key=", ossl_x509_set_public_key, 1);
+    rb_define_method(cX509Cert, "sign", ossl_x509_sign, 2);
+    rb_define_method(cX509Cert, "verify", ossl_x509_verify, 1);
+    rb_define_method(cX509Cert, "check_private_key", ossl_x509_check_private_key, 1);
+    rb_define_method(cX509Cert, "extensions", ossl_x509_get_extensions, 0);
+    rb_define_method(cX509Cert, "extensions=", ossl_x509_set_extensions, 1);
+    rb_define_method(cX509Cert, "add_extension", ossl_x509_add_extension, 1);
+    rb_define_method(cX509Cert, "inspect", ossl_x509_inspect, 0);
+}
diff --git a/ext/openssl/ossl_x509crl.c b/ext/openssl/ossl_x509crl.c
new file mode 100644
index 0000000..f9819f5
--- /dev/null
+++ b/ext/openssl/ossl_x509crl.c
@@ -0,0 +1,546 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2001-2002 Michal Rokos <m.rokos at sh.cvut.cz>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#include "ossl.h"
+
+#define NewX509CRL(klass) \
+    TypedData_Wrap_Struct((klass), &ossl_x509crl_type, 0)
+#define SetX509CRL(obj, crl) do { \
+    if (!(crl)) { \
+	ossl_raise(rb_eRuntimeError, "CRL wasn't initialized!"); \
+    } \
+    RTYPEDDATA_DATA(obj) = (crl); \
+} while (0)
+#define GetX509CRL(obj, crl) do { \
+    TypedData_Get_Struct((obj), X509_CRL, &ossl_x509crl_type, (crl)); \
+    if (!(crl)) { \
+	ossl_raise(rb_eRuntimeError, "CRL wasn't initialized!"); \
+    } \
+} while (0)
+#define SafeGetX509CRL(obj, crl) do { \
+    OSSL_Check_Kind((obj), cX509CRL); \
+    GetX509CRL((obj), (crl)); \
+} while (0)
+
+/*
+ * Classes
+ */
+VALUE cX509CRL;
+VALUE eX509CRLError;
+
+static void
+ossl_x509crl_free(void *ptr)
+{
+    X509_CRL_free(ptr);
+}
+
+static const rb_data_type_t ossl_x509crl_type = {
+    "OpenSSL/X509/CRL",
+    {
+	0, ossl_x509crl_free,
+    },
+    0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
+};
+
+/*
+ * PUBLIC
+ */
+X509_CRL *
+GetX509CRLPtr(VALUE obj)
+{
+    X509_CRL *crl;
+
+    SafeGetX509CRL(obj, crl);
+
+    return crl;
+}
+
+X509_CRL *
+DupX509CRLPtr(VALUE obj)
+{
+    X509_CRL *crl;
+
+    SafeGetX509CRL(obj, crl);
+    X509_CRL_up_ref(crl);
+
+    return crl;
+}
+
+VALUE
+ossl_x509crl_new(X509_CRL *crl)
+{
+    X509_CRL *tmp;
+    VALUE obj;
+
+    obj = NewX509CRL(cX509CRL);
+    tmp = crl ? X509_CRL_dup(crl) : X509_CRL_new();
+    if(!tmp) ossl_raise(eX509CRLError, NULL);
+    SetX509CRL(obj, tmp);
+
+    return obj;
+}
+
+/*
+ * PRIVATE
+ */
+static VALUE
+ossl_x509crl_alloc(VALUE klass)
+{
+    X509_CRL *crl;
+    VALUE obj;
+
+    obj = NewX509CRL(klass);
+    if (!(crl = X509_CRL_new())) {
+	ossl_raise(eX509CRLError, NULL);
+    }
+    SetX509CRL(obj, crl);
+
+    return obj;
+}
+
+static VALUE
+ossl_x509crl_initialize(int argc, VALUE *argv, VALUE self)
+{
+    BIO *in;
+    X509_CRL *crl, *x = DATA_PTR(self);
+    VALUE arg;
+
+    if (rb_scan_args(argc, argv, "01", &arg) == 0) {
+	return self;
+    }
+    arg = ossl_to_der_if_possible(arg);
+    in = ossl_obj2bio(arg);
+    crl = PEM_read_bio_X509_CRL(in, &x, NULL, NULL);
+    DATA_PTR(self) = x;
+    if (!crl) {
+	OSSL_BIO_reset(in);
+	crl = d2i_X509_CRL_bio(in, &x);
+	DATA_PTR(self) = x;
+    }
+    BIO_free(in);
+    if (!crl) ossl_raise(eX509CRLError, NULL);
+
+    return self;
+}
+
+static VALUE
+ossl_x509crl_copy(VALUE self, VALUE other)
+{
+    X509_CRL *a, *b, *crl;
+
+    rb_check_frozen(self);
+    if (self == other) return self;
+    GetX509CRL(self, a);
+    SafeGetX509CRL(other, b);
+    if (!(crl = X509_CRL_dup(b))) {
+	ossl_raise(eX509CRLError, NULL);
+    }
+    X509_CRL_free(a);
+    DATA_PTR(self) = crl;
+
+    return self;
+}
+
+static VALUE
+ossl_x509crl_get_version(VALUE self)
+{
+    X509_CRL *crl;
+    long ver;
+
+    GetX509CRL(self, crl);
+    ver = X509_CRL_get_version(crl);
+
+    return LONG2NUM(ver);
+}
+
+static VALUE
+ossl_x509crl_set_version(VALUE self, VALUE version)
+{
+    X509_CRL *crl;
+    long ver;
+
+    if ((ver = NUM2LONG(version)) < 0) {
+	ossl_raise(eX509CRLError, "version must be >= 0!");
+    }
+    GetX509CRL(self, crl);
+    if (!X509_CRL_set_version(crl, ver)) {
+	ossl_raise(eX509CRLError, NULL);
+    }
+
+    return version;
+}
+
+static VALUE
+ossl_x509crl_get_signature_algorithm(VALUE self)
+{
+    X509_CRL *crl;
+    const X509_ALGOR *alg;
+    BIO *out;
+
+    GetX509CRL(self, crl);
+    if (!(out = BIO_new(BIO_s_mem()))) {
+	ossl_raise(eX509CRLError, NULL);
+    }
+    X509_CRL_get0_signature(crl, NULL, &alg);
+    if (!i2a_ASN1_OBJECT(out, alg->algorithm)) {
+	BIO_free(out);
+	ossl_raise(eX509CRLError, NULL);
+    }
+
+    return ossl_membio2str(out);
+}
+
+static VALUE
+ossl_x509crl_get_issuer(VALUE self)
+{
+    X509_CRL *crl;
+
+    GetX509CRL(self, crl);
+
+    return ossl_x509name_new(X509_CRL_get_issuer(crl)); /* NO DUP - don't free */
+}
+
+static VALUE
+ossl_x509crl_set_issuer(VALUE self, VALUE issuer)
+{
+    X509_CRL *crl;
+
+    GetX509CRL(self, crl);
+
+    if (!X509_CRL_set_issuer_name(crl, GetX509NamePtr(issuer))) { /* DUPs name */
+	ossl_raise(eX509CRLError, NULL);
+    }
+    return issuer;
+}
+
+static VALUE
+ossl_x509crl_get_last_update(VALUE self)
+{
+    X509_CRL *crl;
+
+    GetX509CRL(self, crl);
+
+    return asn1time_to_time(X509_CRL_get0_lastUpdate(crl));
+}
+
+static VALUE
+ossl_x509crl_set_last_update(VALUE self, VALUE time)
+{
+    X509_CRL *crl;
+    ASN1_TIME *asn1time;
+
+    GetX509CRL(self, crl);
+    asn1time = ossl_x509_time_adjust(NULL, time);
+    if (!X509_CRL_set_lastUpdate(crl, asn1time)) {
+	ASN1_TIME_free(asn1time);
+	ossl_raise(eX509CRLError, "X509_CRL_set_lastUpdate");
+    }
+    ASN1_TIME_free(asn1time);
+
+    return time;
+}
+
+static VALUE
+ossl_x509crl_get_next_update(VALUE self)
+{
+    X509_CRL *crl;
+
+    GetX509CRL(self, crl);
+
+    return asn1time_to_time(X509_CRL_get0_nextUpdate(crl));
+}
+
+static VALUE
+ossl_x509crl_set_next_update(VALUE self, VALUE time)
+{
+    X509_CRL *crl;
+    ASN1_TIME *asn1time;
+
+    GetX509CRL(self, crl);
+    asn1time = ossl_x509_time_adjust(NULL, time);
+    if (!X509_CRL_set_nextUpdate(crl, asn1time)) {
+	ASN1_TIME_free(asn1time);
+	ossl_raise(eX509CRLError, "X509_CRL_set_nextUpdate");
+    }
+    ASN1_TIME_free(asn1time);
+
+    return time;
+}
+
+static VALUE
+ossl_x509crl_get_revoked(VALUE self)
+{
+    X509_CRL *crl;
+    int i, num;
+    X509_REVOKED *rev;
+    VALUE ary, revoked;
+
+    GetX509CRL(self, crl);
+    num = sk_X509_REVOKED_num(X509_CRL_get_REVOKED(crl));
+    if (num < 0) {
+	OSSL_Debug("num < 0???");
+	return rb_ary_new();
+    }
+    ary = rb_ary_new2(num);
+    for(i=0; i<num; i++) {
+	/* NO DUP - don't free! */
+	rev = sk_X509_REVOKED_value(X509_CRL_get_REVOKED(crl), i);
+	revoked = ossl_x509revoked_new(rev);
+	rb_ary_push(ary, revoked);
+    }
+
+    return ary;
+}
+
+static VALUE
+ossl_x509crl_set_revoked(VALUE self, VALUE ary)
+{
+    X509_CRL *crl;
+    X509_REVOKED *rev;
+    STACK_OF(X509_REVOKED) *sk;
+    long i;
+
+    Check_Type(ary, T_ARRAY);
+    /* All ary members should be X509 Revoked */
+    for (i=0; i<RARRAY_LEN(ary); i++) {
+	OSSL_Check_Kind(RARRAY_AREF(ary, i), cX509Rev);
+    }
+    GetX509CRL(self, crl);
+    if ((sk = X509_CRL_get_REVOKED(crl))) {
+	while ((rev = sk_X509_REVOKED_pop(sk)))
+	    X509_REVOKED_free(rev);
+    }
+    for (i=0; i<RARRAY_LEN(ary); i++) {
+	rev = DupX509RevokedPtr(RARRAY_AREF(ary, i));
+	if (!X509_CRL_add0_revoked(crl, rev)) { /* NO DUP - don't free! */
+	    X509_REVOKED_free(rev);
+	    ossl_raise(eX509CRLError, "X509_CRL_add0_revoked");
+	}
+    }
+    X509_CRL_sort(crl);
+
+    return ary;
+}
+
+static VALUE
+ossl_x509crl_add_revoked(VALUE self, VALUE revoked)
+{
+    X509_CRL *crl;
+    X509_REVOKED *rev;
+
+    GetX509CRL(self, crl);
+    rev = DupX509RevokedPtr(revoked);
+    if (!X509_CRL_add0_revoked(crl, rev)) { /* NO DUP - don't free! */
+	X509_REVOKED_free(rev);
+	ossl_raise(eX509CRLError, "X509_CRL_add0_revoked");
+    }
+    X509_CRL_sort(crl);
+
+    return revoked;
+}
+
+static VALUE
+ossl_x509crl_sign(VALUE self, VALUE key, VALUE digest)
+{
+    X509_CRL *crl;
+    EVP_PKEY *pkey;
+    const EVP_MD *md;
+
+    GetX509CRL(self, crl);
+    pkey = GetPrivPKeyPtr(key); /* NO NEED TO DUP */
+    md = GetDigestPtr(digest);
+    if (!X509_CRL_sign(crl, pkey, md)) {
+	ossl_raise(eX509CRLError, NULL);
+    }
+
+    return self;
+}
+
+static VALUE
+ossl_x509crl_verify(VALUE self, VALUE key)
+{
+    X509_CRL *crl;
+
+    GetX509CRL(self, crl);
+    switch (X509_CRL_verify(crl, GetPKeyPtr(key))) {
+      case 1:
+	return Qtrue;
+      case 0:
+	ossl_clear_error();
+	return Qfalse;
+      default:
+	ossl_raise(eX509CRLError, NULL);
+    }
+}
+
+static VALUE
+ossl_x509crl_to_der(VALUE self)
+{
+    X509_CRL *crl;
+    BIO *out;
+
+    GetX509CRL(self, crl);
+    if (!(out = BIO_new(BIO_s_mem()))) {
+	ossl_raise(eX509CRLError, NULL);
+    }
+    if (!i2d_X509_CRL_bio(out, crl)) {
+	BIO_free(out);
+	ossl_raise(eX509CRLError, NULL);
+    }
+
+    return ossl_membio2str(out);
+}
+
+static VALUE
+ossl_x509crl_to_pem(VALUE self)
+{
+    X509_CRL *crl;
+    BIO *out;
+
+    GetX509CRL(self, crl);
+    if (!(out = BIO_new(BIO_s_mem()))) {
+	ossl_raise(eX509CRLError, NULL);
+    }
+    if (!PEM_write_bio_X509_CRL(out, crl)) {
+	BIO_free(out);
+	ossl_raise(eX509CRLError, NULL);
+    }
+
+    return ossl_membio2str(out);
+}
+
+static VALUE
+ossl_x509crl_to_text(VALUE self)
+{
+    X509_CRL *crl;
+    BIO *out;
+
+    GetX509CRL(self, crl);
+    if (!(out = BIO_new(BIO_s_mem()))) {
+	ossl_raise(eX509CRLError, NULL);
+    }
+    if (!X509_CRL_print(out, crl)) {
+	BIO_free(out);
+	ossl_raise(eX509CRLError, NULL);
+    }
+
+    return ossl_membio2str(out);
+}
+
+/*
+ * Gets X509v3 extensions as array of X509Ext objects
+ */
+static VALUE
+ossl_x509crl_get_extensions(VALUE self)
+{
+    X509_CRL *crl;
+    int count, i;
+    X509_EXTENSION *ext;
+    VALUE ary;
+
+    GetX509CRL(self, crl);
+    count = X509_CRL_get_ext_count(crl);
+    if (count < 0) {
+	OSSL_Debug("count < 0???");
+	return rb_ary_new();
+    }
+    ary = rb_ary_new2(count);
+    for (i=0; i<count; i++) {
+	ext = X509_CRL_get_ext(crl, i); /* NO DUP - don't free! */
+	rb_ary_push(ary, ossl_x509ext_new(ext));
+    }
+
+    return ary;
+}
+
+/*
+ * Sets X509_EXTENSIONs
+ */
+static VALUE
+ossl_x509crl_set_extensions(VALUE self, VALUE ary)
+{
+    X509_CRL *crl;
+    X509_EXTENSION *ext;
+    long i;
+
+    Check_Type(ary, T_ARRAY);
+    /* All ary members should be X509 Extensions */
+    for (i=0; i<RARRAY_LEN(ary); i++) {
+	OSSL_Check_Kind(RARRAY_AREF(ary, i), cX509Ext);
+    }
+    GetX509CRL(self, crl);
+    while ((ext = X509_CRL_delete_ext(crl, 0)))
+	X509_EXTENSION_free(ext);
+    for (i=0; i<RARRAY_LEN(ary); i++) {
+	ext = GetX509ExtPtr(RARRAY_AREF(ary, i)); /* NO NEED TO DUP */
+	if (!X509_CRL_add_ext(crl, ext, -1)) {
+	    ossl_raise(eX509CRLError, NULL);
+	}
+    }
+
+    return ary;
+}
+
+static VALUE
+ossl_x509crl_add_extension(VALUE self, VALUE extension)
+{
+    X509_CRL *crl;
+    X509_EXTENSION *ext;
+
+    GetX509CRL(self, crl);
+    ext = GetX509ExtPtr(extension);
+    if (!X509_CRL_add_ext(crl, ext, -1)) {
+	ossl_raise(eX509CRLError, NULL);
+    }
+
+    return extension;
+}
+
+/*
+ * INIT
+ */
+void
+Init_ossl_x509crl(void)
+{
+#if 0
+    mOSSL = rb_define_module("OpenSSL");
+    eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError);
+    mX509 = rb_define_module_under(mOSSL, "X509");
+#endif
+
+    eX509CRLError = rb_define_class_under(mX509, "CRLError", eOSSLError);
+
+    cX509CRL = rb_define_class_under(mX509, "CRL", rb_cObject);
+
+    rb_define_alloc_func(cX509CRL, ossl_x509crl_alloc);
+    rb_define_method(cX509CRL, "initialize", ossl_x509crl_initialize, -1);
+    rb_define_copy_func(cX509CRL, ossl_x509crl_copy);
+
+    rb_define_method(cX509CRL, "version", ossl_x509crl_get_version, 0);
+    rb_define_method(cX509CRL, "version=", ossl_x509crl_set_version, 1);
+    rb_define_method(cX509CRL, "signature_algorithm", ossl_x509crl_get_signature_algorithm, 0);
+    rb_define_method(cX509CRL, "issuer", ossl_x509crl_get_issuer, 0);
+    rb_define_method(cX509CRL, "issuer=", ossl_x509crl_set_issuer, 1);
+    rb_define_method(cX509CRL, "last_update", ossl_x509crl_get_last_update, 0);
+    rb_define_method(cX509CRL, "last_update=", ossl_x509crl_set_last_update, 1);
+    rb_define_method(cX509CRL, "next_update", ossl_x509crl_get_next_update, 0);
+    rb_define_method(cX509CRL, "next_update=", ossl_x509crl_set_next_update, 1);
+    rb_define_method(cX509CRL, "revoked", ossl_x509crl_get_revoked, 0);
+    rb_define_method(cX509CRL, "revoked=", ossl_x509crl_set_revoked, 1);
+    rb_define_method(cX509CRL, "add_revoked", ossl_x509crl_add_revoked, 1);
+    rb_define_method(cX509CRL, "sign", ossl_x509crl_sign, 2);
+    rb_define_method(cX509CRL, "verify", ossl_x509crl_verify, 1);
+    rb_define_method(cX509CRL, "to_der", ossl_x509crl_to_der, 0);
+    rb_define_method(cX509CRL, "to_pem", ossl_x509crl_to_pem, 0);
+    rb_define_alias(cX509CRL, "to_s", "to_pem");
+    rb_define_method(cX509CRL, "to_text", ossl_x509crl_to_text, 0);
+    rb_define_method(cX509CRL, "extensions", ossl_x509crl_get_extensions, 0);
+    rb_define_method(cX509CRL, "extensions=", ossl_x509crl_set_extensions, 1);
+    rb_define_method(cX509CRL, "add_extension", ossl_x509crl_add_extension, 1);
+}
diff --git a/ext/openssl/ossl_x509ext.c b/ext/openssl/ossl_x509ext.c
new file mode 100644
index 0000000..b92b078
--- /dev/null
+++ b/ext/openssl/ossl_x509ext.c
@@ -0,0 +1,480 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2001-2002  Michal Rokos <m.rokos at sh.cvut.cz>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#include "ossl.h"
+
+#define NewX509Ext(klass) \
+    TypedData_Wrap_Struct((klass), &ossl_x509ext_type, 0)
+#define SetX509Ext(obj, ext) do { \
+    if (!(ext)) { \
+	ossl_raise(rb_eRuntimeError, "EXT wasn't initialized!"); \
+    } \
+    RTYPEDDATA_DATA(obj) = (ext); \
+} while (0)
+#define GetX509Ext(obj, ext) do { \
+    TypedData_Get_Struct((obj), X509_EXTENSION, &ossl_x509ext_type, (ext)); \
+    if (!(ext)) { \
+	ossl_raise(rb_eRuntimeError, "EXT wasn't initialized!"); \
+    } \
+} while (0)
+#define SafeGetX509Ext(obj, ext) do { \
+    OSSL_Check_Kind((obj), cX509Ext); \
+    GetX509Ext((obj), (ext)); \
+} while (0)
+#define MakeX509ExtFactory(klass, obj, ctx) do { \
+    (obj) = TypedData_Wrap_Struct((klass), &ossl_x509extfactory_type, 0); \
+    if (!((ctx) = OPENSSL_malloc(sizeof(X509V3_CTX)))) \
+        ossl_raise(rb_eRuntimeError, "CTX wasn't allocated!"); \
+    X509V3_set_ctx((ctx), NULL, NULL, NULL, NULL, 0); \
+    RTYPEDDATA_DATA(obj) = (ctx); \
+} while (0)
+#define GetX509ExtFactory(obj, ctx) do { \
+    TypedData_Get_Struct((obj), X509V3_CTX, &ossl_x509extfactory_type, (ctx)); \
+    if (!(ctx)) { \
+	ossl_raise(rb_eRuntimeError, "CTX wasn't initialized!"); \
+    } \
+} while (0)
+
+/*
+ * Classes
+ */
+VALUE cX509Ext;
+VALUE cX509ExtFactory;
+VALUE eX509ExtError;
+
+static void
+ossl_x509ext_free(void *ptr)
+{
+    X509_EXTENSION_free(ptr);
+}
+
+static const rb_data_type_t ossl_x509ext_type = {
+    "OpenSSL/X509/EXTENSION",
+    {
+	0, ossl_x509ext_free,
+    },
+    0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
+};
+
+/*
+ * Public
+ */
+VALUE
+ossl_x509ext_new(X509_EXTENSION *ext)
+{
+    X509_EXTENSION *new;
+    VALUE obj;
+
+    obj = NewX509Ext(cX509Ext);
+    if (!ext) {
+	new = X509_EXTENSION_new();
+    } else {
+	new = X509_EXTENSION_dup(ext);
+    }
+    if (!new) {
+	ossl_raise(eX509ExtError, NULL);
+    }
+    SetX509Ext(obj, new);
+
+    return obj;
+}
+
+X509_EXTENSION *
+GetX509ExtPtr(VALUE obj)
+{
+    X509_EXTENSION *ext;
+
+    SafeGetX509Ext(obj, ext);
+
+    return ext;
+}
+
+/*
+ * Private
+ */
+/*
+ * Ext factory
+ */
+static void
+ossl_x509extfactory_free(void *ctx)
+{
+    OPENSSL_free(ctx);
+}
+
+static const rb_data_type_t ossl_x509extfactory_type = {
+    "OpenSSL/X509/EXTENSION/Factory",
+    {
+	0, ossl_x509extfactory_free,
+    },
+    0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
+};
+
+static VALUE
+ossl_x509extfactory_alloc(VALUE klass)
+{
+    X509V3_CTX *ctx;
+    VALUE obj;
+
+    MakeX509ExtFactory(klass, obj, ctx);
+    rb_iv_set(obj, "@config", Qnil);
+
+    return obj;
+}
+
+static VALUE
+ossl_x509extfactory_set_issuer_cert(VALUE self, VALUE cert)
+{
+    X509V3_CTX *ctx;
+
+    GetX509ExtFactory(self, ctx);
+    rb_iv_set(self, "@issuer_certificate", cert);
+    ctx->issuer_cert = GetX509CertPtr(cert); /* NO DUP NEEDED */
+
+    return cert;
+}
+
+static VALUE
+ossl_x509extfactory_set_subject_cert(VALUE self, VALUE cert)
+{
+    X509V3_CTX *ctx;
+
+    GetX509ExtFactory(self, ctx);
+    rb_iv_set(self, "@subject_certificate", cert);
+    ctx->subject_cert = GetX509CertPtr(cert); /* NO DUP NEEDED */
+
+    return cert;
+}
+
+static VALUE
+ossl_x509extfactory_set_subject_req(VALUE self, VALUE req)
+{
+    X509V3_CTX *ctx;
+
+    GetX509ExtFactory(self, ctx);
+    rb_iv_set(self, "@subject_request", req);
+    ctx->subject_req = GetX509ReqPtr(req); /* NO DUP NEEDED */
+
+    return req;
+}
+
+static VALUE
+ossl_x509extfactory_set_crl(VALUE self, VALUE crl)
+{
+    X509V3_CTX *ctx;
+
+    GetX509ExtFactory(self, ctx);
+    rb_iv_set(self, "@crl", crl);
+    ctx->crl = GetX509CRLPtr(crl); /* NO DUP NEEDED */
+
+    return crl;
+}
+
+static VALUE
+ossl_x509extfactory_initialize(int argc, VALUE *argv, VALUE self)
+{
+    /*X509V3_CTX *ctx;*/
+    VALUE issuer_cert, subject_cert, subject_req, crl;
+
+    /*GetX509ExtFactory(self, ctx);*/
+
+    rb_scan_args(argc, argv, "04",
+		 &issuer_cert, &subject_cert, &subject_req, &crl);
+    if (!NIL_P(issuer_cert))
+	ossl_x509extfactory_set_issuer_cert(self, issuer_cert);
+    if (!NIL_P(subject_cert))
+	ossl_x509extfactory_set_subject_cert(self, subject_cert);
+    if (!NIL_P(subject_req))
+	ossl_x509extfactory_set_subject_req(self, subject_req);
+    if (!NIL_P(crl))
+	ossl_x509extfactory_set_crl(self, crl);
+
+    return self;
+}
+
+/*
+ * call-seq:
+ *   ef.create_ext(ln_or_sn, "value", critical = false) -> X509::Extension
+ *   ef.create_ext(ln_or_sn, "critical,value")          -> X509::Extension
+ *
+ * Creates a new X509::Extension with passed values. See also x509v3_config(5).
+ */
+static VALUE
+ossl_x509extfactory_create_ext(int argc, VALUE *argv, VALUE self)
+{
+    X509V3_CTX *ctx;
+    X509_EXTENSION *ext;
+    VALUE oid, value, critical, valstr, obj;
+    int nid;
+    VALUE rconf;
+    CONF *conf;
+
+    rb_scan_args(argc, argv, "21", &oid, &value, &critical);
+    StringValueCStr(oid);
+    StringValue(value);
+    if(NIL_P(critical)) critical = Qfalse;
+
+    nid = OBJ_ln2nid(RSTRING_PTR(oid));
+    if(!nid) nid = OBJ_sn2nid(RSTRING_PTR(oid));
+    if(!nid) ossl_raise(eX509ExtError, "unknown OID `%"PRIsVALUE"'", oid);
+
+    valstr = rb_str_new2(RTEST(critical) ? "critical," : "");
+    rb_str_append(valstr, value);
+    StringValueCStr(valstr);
+
+    GetX509ExtFactory(self, ctx);
+    obj = NewX509Ext(cX509Ext);
+    rconf = rb_iv_get(self, "@config");
+    conf = NIL_P(rconf) ? NULL : DupConfigPtr(rconf);
+    X509V3_set_nconf(ctx, conf);
+    ext = X509V3_EXT_nconf_nid(conf, ctx, nid, RSTRING_PTR(valstr));
+    X509V3_set_ctx_nodb(ctx);
+    NCONF_free(conf);
+    if (!ext){
+	ossl_raise(eX509ExtError, "%"PRIsVALUE" = %"PRIsVALUE, oid, valstr);
+    }
+    SetX509Ext(obj, ext);
+
+    return obj;
+}
+
+/*
+ * Ext
+ */
+static VALUE
+ossl_x509ext_alloc(VALUE klass)
+{
+    X509_EXTENSION *ext;
+    VALUE obj;
+
+    obj = NewX509Ext(klass);
+    if(!(ext = X509_EXTENSION_new())){
+	ossl_raise(eX509ExtError, NULL);
+    }
+    SetX509Ext(obj, ext);
+
+    return obj;
+}
+
+/*
+ * call-seq:
+ *    OpenSSL::X509::Extension.new asn1
+ *    OpenSSL::X509::Extension.new name, value
+ *    OpenSSL::X509::Extension.new name, value, critical
+ *
+ * Creates an X509 extension.
+ *
+ * The extension may be created from +asn1+ data or from an extension +name+
+ * and +value+.  The +name+ may be either an OID or an extension name.  If
+ * +critical+ is true the extension is marked critical.
+ */
+static VALUE
+ossl_x509ext_initialize(int argc, VALUE *argv, VALUE self)
+{
+    VALUE oid, value, critical;
+    const unsigned char *p;
+    X509_EXTENSION *ext, *x;
+
+    GetX509Ext(self, ext);
+    if(rb_scan_args(argc, argv, "12", &oid, &value, &critical) == 1){
+	oid = ossl_to_der_if_possible(oid);
+	StringValue(oid);
+	p = (unsigned char *)RSTRING_PTR(oid);
+	x = d2i_X509_EXTENSION(&ext, &p, RSTRING_LEN(oid));
+	DATA_PTR(self) = ext;
+	if(!x)
+	    ossl_raise(eX509ExtError, NULL);
+	return self;
+    }
+    rb_funcall(self, rb_intern("oid="), 1, oid);
+    rb_funcall(self, rb_intern("value="), 1, value);
+    if(argc > 2) rb_funcall(self, rb_intern("critical="), 1, critical);
+
+    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)
+{
+    X509_EXTENSION *ext;
+    ASN1_OBJECT *obj;
+
+    GetX509Ext(self, ext);
+    obj = OBJ_txt2obj(StringValueCStr(oid), 0);
+    if (!obj)
+	ossl_raise(eX509ExtError, "OBJ_txt2obj");
+    if (!X509_EXTENSION_set_object(ext, obj)) {
+	ASN1_OBJECT_free(obj);
+	ossl_raise(eX509ExtError, "X509_EXTENSION_set_object");
+    }
+    ASN1_OBJECT_free(obj);
+
+    return oid;
+}
+
+static VALUE
+ossl_x509ext_set_value(VALUE self, VALUE data)
+{
+    X509_EXTENSION *ext;
+    ASN1_OCTET_STRING *asn1s;
+
+    GetX509Ext(self, ext);
+    data = ossl_to_der_if_possible(data);
+    StringValue(data);
+    asn1s = X509_EXTENSION_get_data(ext);
+
+    if (!ASN1_OCTET_STRING_set(asn1s, (unsigned char *)RSTRING_PTR(data),
+			       RSTRING_LENINT(data))) {
+	ossl_raise(eX509ExtError, "ASN1_OCTET_STRING_set");
+    }
+
+    return data;
+}
+
+static VALUE
+ossl_x509ext_set_critical(VALUE self, VALUE flag)
+{
+    X509_EXTENSION *ext;
+
+    GetX509Ext(self, ext);
+    X509_EXTENSION_set_critical(ext, RTEST(flag) ? 1 : 0);
+
+    return flag;
+}
+
+static VALUE
+ossl_x509ext_get_oid(VALUE obj)
+{
+    X509_EXTENSION *ext;
+    ASN1_OBJECT *extobj;
+    BIO *out;
+    VALUE ret;
+    int nid;
+
+    GetX509Ext(obj, ext);
+    extobj = X509_EXTENSION_get_object(ext);
+    if ((nid = OBJ_obj2nid(extobj)) != NID_undef)
+	ret = rb_str_new2(OBJ_nid2sn(nid));
+    else{
+	if (!(out = BIO_new(BIO_s_mem())))
+	    ossl_raise(eX509ExtError, NULL);
+	i2a_ASN1_OBJECT(out, extobj);
+	ret = ossl_membio2str(out);
+    }
+
+    return ret;
+}
+
+static VALUE
+ossl_x509ext_get_value(VALUE obj)
+{
+    X509_EXTENSION *ext;
+    BIO *out;
+    VALUE ret;
+
+    GetX509Ext(obj, ext);
+    if (!(out = BIO_new(BIO_s_mem())))
+	ossl_raise(eX509ExtError, NULL);
+    if (!X509V3_EXT_print(out, ext, 0, 0))
+	ASN1_STRING_print(out, (ASN1_STRING *)X509_EXTENSION_get_data(ext));
+    ret = ossl_membio2str(out);
+
+    return ret;
+}
+
+static VALUE
+ossl_x509ext_get_critical(VALUE obj)
+{
+    X509_EXTENSION *ext;
+
+    GetX509Ext(obj, ext);
+    return X509_EXTENSION_get_critical(ext) ? Qtrue : Qfalse;
+}
+
+static VALUE
+ossl_x509ext_to_der(VALUE obj)
+{
+    X509_EXTENSION *ext;
+    unsigned char *p;
+    long len;
+    VALUE str;
+
+    GetX509Ext(obj, ext);
+    if((len = i2d_X509_EXTENSION(ext, NULL)) <= 0)
+	ossl_raise(eX509ExtError, NULL);
+    str = rb_str_new(0, len);
+    p = (unsigned char *)RSTRING_PTR(str);
+    if(i2d_X509_EXTENSION(ext, &p) < 0)
+	ossl_raise(eX509ExtError, NULL);
+    ossl_str_adjust(str, p);
+
+    return str;
+}
+
+/*
+ * INIT
+ */
+void
+Init_ossl_x509ext(void)
+{
+#if 0
+    mOSSL = rb_define_module("OpenSSL");
+    eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError);
+    mX509 = rb_define_module_under(mOSSL, "X509");
+#endif
+
+    eX509ExtError = rb_define_class_under(mX509, "ExtensionError", eOSSLError);
+
+    cX509ExtFactory = rb_define_class_under(mX509, "ExtensionFactory", rb_cObject);
+
+    rb_define_alloc_func(cX509ExtFactory, ossl_x509extfactory_alloc);
+    rb_define_method(cX509ExtFactory, "initialize", ossl_x509extfactory_initialize, -1);
+
+    rb_attr(cX509ExtFactory, rb_intern("issuer_certificate"), 1, 0, Qfalse);
+    rb_attr(cX509ExtFactory, rb_intern("subject_certificate"), 1, 0, Qfalse);
+    rb_attr(cX509ExtFactory, rb_intern("subject_request"), 1, 0, Qfalse);
+    rb_attr(cX509ExtFactory, rb_intern("crl"), 1, 0, Qfalse);
+    rb_attr(cX509ExtFactory, rb_intern("config"), 1, 1, Qfalse);
+
+    rb_define_method(cX509ExtFactory, "issuer_certificate=", ossl_x509extfactory_set_issuer_cert, 1);
+    rb_define_method(cX509ExtFactory, "subject_certificate=", ossl_x509extfactory_set_subject_cert, 1);
+    rb_define_method(cX509ExtFactory, "subject_request=", ossl_x509extfactory_set_subject_req, 1);
+    rb_define_method(cX509ExtFactory, "crl=", ossl_x509extfactory_set_crl, 1);
+    rb_define_method(cX509ExtFactory, "create_ext", ossl_x509extfactory_create_ext, -1);
+
+    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);
+    rb_define_method(cX509Ext, "oid", ossl_x509ext_get_oid, 0);
+    rb_define_method(cX509Ext, "value", ossl_x509ext_get_value, 0);
+    rb_define_method(cX509Ext, "critical?", ossl_x509ext_get_critical, 0);
+    rb_define_method(cX509Ext, "to_der", ossl_x509ext_to_der, 0);
+}
diff --git a/ext/openssl/ossl_x509name.c b/ext/openssl/ossl_x509name.c
new file mode 100644
index 0000000..ac98c1b
--- /dev/null
+++ b/ext/openssl/ossl_x509name.c
@@ -0,0 +1,545 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2001 Michal Rokos <m.rokos at sh.cvut.cz>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#include "ossl.h"
+
+#define NewX509Name(klass) \
+    TypedData_Wrap_Struct((klass), &ossl_x509name_type, 0)
+#define SetX509Name(obj, name) do { \
+    if (!(name)) { \
+	ossl_raise(rb_eRuntimeError, "Name wasn't initialized."); \
+    } \
+    RTYPEDDATA_DATA(obj) = (name); \
+} while (0)
+#define GetX509Name(obj, name) do { \
+    TypedData_Get_Struct((obj), X509_NAME, &ossl_x509name_type, (name)); \
+    if (!(name)) { \
+	ossl_raise(rb_eRuntimeError, "Name wasn't initialized."); \
+    } \
+} while (0)
+#define SafeGetX509Name(obj, name) do { \
+    OSSL_Check_Kind((obj), cX509Name); \
+    GetX509Name((obj), (name)); \
+} while (0)
+
+#define OBJECT_TYPE_TEMPLATE \
+  rb_const_get(cX509Name, rb_intern("OBJECT_TYPE_TEMPLATE"))
+#define DEFAULT_OBJECT_TYPE \
+  rb_const_get(cX509Name, rb_intern("DEFAULT_OBJECT_TYPE"))
+
+/*
+ * Classes
+ */
+VALUE cX509Name;
+VALUE eX509NameError;
+
+static void
+ossl_x509name_free(void *ptr)
+{
+    X509_NAME_free(ptr);
+}
+
+static const rb_data_type_t ossl_x509name_type = {
+    "OpenSSL/X509/NAME",
+    {
+	0, ossl_x509name_free,
+    },
+    0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
+};
+
+/*
+ * Public
+ */
+VALUE
+ossl_x509name_new(X509_NAME *name)
+{
+    X509_NAME *new;
+    VALUE obj;
+
+    obj = NewX509Name(cX509Name);
+    if (!name) {
+	new = X509_NAME_new();
+    } else {
+	new = X509_NAME_dup(name);
+    }
+    if (!new) {
+	ossl_raise(eX509NameError, NULL);
+    }
+    SetX509Name(obj, new);
+
+    return obj;
+}
+
+X509_NAME *
+GetX509NamePtr(VALUE obj)
+{
+    X509_NAME *name;
+
+    SafeGetX509Name(obj, name);
+
+    return name;
+}
+
+/*
+ * Private
+ */
+static VALUE
+ossl_x509name_alloc(VALUE klass)
+{
+    X509_NAME *name;
+    VALUE obj;
+
+    obj = NewX509Name(klass);
+    if (!(name = X509_NAME_new())) {
+	ossl_raise(eX509NameError, NULL);
+    }
+    SetX509Name(obj, name);
+
+    return obj;
+}
+
+static ID id_aref;
+static VALUE ossl_x509name_add_entry(int, VALUE*, VALUE);
+#define rb_aref(obj, key) rb_funcall((obj), id_aref, 1, (key))
+
+static VALUE
+ossl_x509name_init_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, args))
+{
+    VALUE self = rb_ary_entry(args, 0);
+    VALUE template = rb_ary_entry(args, 1);
+    VALUE entry[3];
+
+    Check_Type(i, T_ARRAY);
+    entry[0] = rb_ary_entry(i, 0);
+    entry[1] = rb_ary_entry(i, 1);
+    entry[2] = rb_ary_entry(i, 2);
+    if(NIL_P(entry[2])) entry[2] = rb_aref(template, entry[0]);
+    if(NIL_P(entry[2])) entry[2] = DEFAULT_OBJECT_TYPE;
+    ossl_x509name_add_entry(3, entry, self);
+
+    return Qnil;
+}
+
+/*
+ * call-seq:
+ *    X509::Name.new                               => name
+ *    X509::Name.new(der)                          => name
+ *    X509::Name.new(distinguished_name)           => name
+ *    X509::Name.new(distinguished_name, template) => name
+ *
+ * Creates a new Name.
+ *
+ * A name may be created from a DER encoded string +der+, an Array
+ * representing a +distinguished_name+ or a +distinguished_name+ along with a
+ * +template+.
+ *
+ *   name = OpenSSL::X509::Name.new [['CN', 'nobody'], ['DC', 'example']]
+ *
+ *   name = OpenSSL::X509::Name.new name.to_der
+ *
+ * See add_entry for a description of the +distinguished_name+ Array's
+ * contents
+ */
+static VALUE
+ossl_x509name_initialize(int argc, VALUE *argv, VALUE self)
+{
+    X509_NAME *name;
+    VALUE arg, template;
+
+    GetX509Name(self, name);
+    if (rb_scan_args(argc, argv, "02", &arg, &template) == 0) {
+	return self;
+    }
+    else {
+	VALUE tmp = rb_check_array_type(arg);
+	if (!NIL_P(tmp)) {
+	    VALUE args;
+	    if(NIL_P(template)) template = OBJECT_TYPE_TEMPLATE;
+	    args = rb_ary_new3(2, self, template);
+	    rb_block_call(tmp, rb_intern("each"), 0, 0, ossl_x509name_init_i, args);
+	}
+	else{
+	    const unsigned char *p;
+	    VALUE str = ossl_to_der_if_possible(arg);
+	    X509_NAME *x;
+	    StringValue(str);
+	    p = (unsigned char *)RSTRING_PTR(str);
+	    x = d2i_X509_NAME(&name, &p, RSTRING_LEN(str));
+	    DATA_PTR(self) = name;
+	    if(!x){
+		ossl_raise(eX509NameError, NULL);
+	    }
+	}
+    }
+
+    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
+ *
+ * Adds a new entry with the given +oid+ and +value+ to this name.  The +oid+
+ * is an object identifier defined in ASN.1.  Some common OIDs are:
+ *
+ * C::  Country Name
+ * CN:: Common Name
+ * DC:: Domain Component
+ * O::  Organization Name
+ * OU:: Organizational Unit Name
+ * ST:: State or Province Name
+ */
+static
+VALUE ossl_x509name_add_entry(int argc, VALUE *argv, VALUE self)
+{
+    X509_NAME *name;
+    VALUE oid, value, type;
+    const char *oid_name;
+
+    rb_scan_args(argc, argv, "21", &oid, &value, &type);
+    oid_name = StringValueCStr(oid);
+    StringValue(value);
+    if(NIL_P(type)) type = rb_aref(OBJECT_TYPE_TEMPLATE, oid);
+    GetX509Name(self, name);
+    if (!X509_NAME_add_entry_by_txt(name, oid_name, NUM2INT(type),
+		(const unsigned char *)RSTRING_PTR(value), RSTRING_LENINT(value), -1, 0)) {
+	ossl_raise(eX509NameError, NULL);
+    }
+
+    return self;
+}
+
+static VALUE
+ossl_x509name_to_s_old(VALUE self)
+{
+    X509_NAME *name;
+    char *buf;
+    VALUE str;
+
+    GetX509Name(self, name);
+    buf = X509_NAME_oneline(name, NULL, 0);
+    str = rb_str_new2(buf);
+    OPENSSL_free(buf);
+
+    return str;
+}
+
+/*
+ * call-seq:
+ *    name.to_s => string
+ *    name.to_s(flags) => string
+ *
+ * Returns this name as a Distinguished Name string.  +flags+ may be one of:
+ *
+ * * OpenSSL::X509::Name::COMPAT
+ * * OpenSSL::X509::Name::RFC2253
+ * * OpenSSL::X509::Name::ONELINE
+ * * OpenSSL::X509::Name::MULTILINE
+ */
+static VALUE
+ossl_x509name_to_s(int argc, VALUE *argv, VALUE self)
+{
+    X509_NAME *name;
+    VALUE flag, str;
+    BIO *out;
+    unsigned long iflag;
+
+    rb_scan_args(argc, argv, "01", &flag);
+    if (NIL_P(flag))
+	return ossl_x509name_to_s_old(self);
+    else iflag = NUM2ULONG(flag);
+    if (!(out = BIO_new(BIO_s_mem())))
+	ossl_raise(eX509NameError, NULL);
+    GetX509Name(self, name);
+    if (!X509_NAME_print_ex(out, name, 0, iflag)){
+	BIO_free(out);
+	ossl_raise(eX509NameError, NULL);
+    }
+    str = ossl_membio2str(out);
+
+    return str;
+}
+
+/*
+ * call-seq:
+ *    name.to_a => [[name, data, type], ...]
+ *
+ * Returns an Array representation of the distinguished name suitable for
+ * passing to ::new
+ */
+static VALUE
+ossl_x509name_to_a(VALUE self)
+{
+    X509_NAME *name;
+    X509_NAME_ENTRY *entry;
+    int i,entries,nid;
+    char long_name[512];
+    const char *short_name;
+    VALUE ary, vname, ret;
+    ASN1_STRING *value;
+
+    GetX509Name(self, name);
+    entries = X509_NAME_entry_count(name);
+    if (entries < 0) {
+	OSSL_Debug("name entries < 0!");
+	return rb_ary_new();
+    }
+    ret = rb_ary_new2(entries);
+    for (i=0; i<entries; i++) {
+	if (!(entry = X509_NAME_get_entry(name, i))) {
+	    ossl_raise(eX509NameError, NULL);
+	}
+	if (!i2t_ASN1_OBJECT(long_name, sizeof(long_name),
+			     X509_NAME_ENTRY_get_object(entry))) {
+	    ossl_raise(eX509NameError, NULL);
+	}
+	nid = OBJ_ln2nid(long_name);
+	if (nid == NID_undef) {
+	    vname = rb_str_new2((const char *) &long_name);
+	} else {
+	    short_name = OBJ_nid2sn(nid);
+	    vname = rb_str_new2(short_name); /*do not free*/
+	}
+	value = X509_NAME_ENTRY_get_data(entry);
+	ary = rb_ary_new3(3, vname, asn1str_to_str(value), INT2NUM(value->type));
+	rb_ary_push(ret, ary);
+    }
+    return ret;
+}
+
+static int
+ossl_x509name_cmp0(VALUE self, VALUE other)
+{
+    X509_NAME *name1, *name2;
+
+    GetX509Name(self, name1);
+    SafeGetX509Name(other, name2);
+
+    return X509_NAME_cmp(name1, name2);
+}
+
+/*
+ * call-seq:
+ *    name.cmp other => integer
+ *    name.<=> other => integer
+ *
+ * Compares this Name with +other+ and returns 0 if they are the same and -1 or
+ * +1 if they are greater or less than each other respectively.
+ */
+static VALUE
+ossl_x509name_cmp(VALUE self, VALUE other)
+{
+    int result;
+
+    result = ossl_x509name_cmp0(self, other);
+    if (result < 0) return INT2FIX(-1);
+    if (result > 1) return INT2FIX(1);
+
+    return INT2FIX(0);
+}
+
+/*
+ * call-seq:
+ *   name.eql? other => boolean
+ *
+ * Returns true if +name+ and +other+ refer to the same hash key.
+ */
+static VALUE
+ossl_x509name_eql(VALUE self, VALUE other)
+{
+    if (!rb_obj_is_kind_of(other, cX509Name))
+	return Qfalse;
+
+    return ossl_x509name_cmp0(self, other) == 0 ? Qtrue : Qfalse;
+}
+
+/*
+ * call-seq:
+ *    name.hash => integer
+ *
+ * The hash value returned is suitable for use as a certificate's filename in
+ * a CA path.
+ */
+static VALUE
+ossl_x509name_hash(VALUE self)
+{
+    X509_NAME *name;
+    unsigned long hash;
+
+    GetX509Name(self, name);
+
+    hash = X509_NAME_hash(name);
+
+    return ULONG2NUM(hash);
+}
+
+#ifdef HAVE_X509_NAME_HASH_OLD
+/*
+ * call-seq:
+ *    name.hash_old => integer
+ *
+ * Returns an MD5 based hash used in OpenSSL 0.9.X.
+ */
+static VALUE
+ossl_x509name_hash_old(VALUE self)
+{
+    X509_NAME *name;
+    unsigned long hash;
+
+    GetX509Name(self, name);
+
+    hash = X509_NAME_hash_old(name);
+
+    return ULONG2NUM(hash);
+}
+#endif
+
+/*
+ * call-seq:
+ *    name.to_der => string
+ *
+ * Converts the name to DER encoding
+ */
+static VALUE
+ossl_x509name_to_der(VALUE self)
+{
+    X509_NAME *name;
+    VALUE str;
+    long len;
+    unsigned char *p;
+
+    GetX509Name(self, name);
+    if((len = i2d_X509_NAME(name, NULL)) <= 0)
+	ossl_raise(eX509NameError, NULL);
+    str = rb_str_new(0, len);
+    p = (unsigned char *)RSTRING_PTR(str);
+    if(i2d_X509_NAME(name, &p) <= 0)
+	ossl_raise(eX509NameError, NULL);
+    ossl_str_adjust(str, p);
+
+    return str;
+}
+
+/*
+ * Document-class: OpenSSL::X509::Name
+ *
+ * An X.509 name represents a hostname, email address or other entity
+ * associated with a public key.
+ *
+ * You can create a Name by parsing a distinguished name String or by
+ * supplying the distinguished name as an Array.
+ *
+ *   name = OpenSSL::X509::Name.parse 'CN=nobody/DC=example'
+ *
+ *   name = OpenSSL::X509::Name.new [['CN', 'nobody'], ['DC', 'example']]
+ */
+
+void
+Init_ossl_x509name(void)
+{
+    VALUE utf8str, ptrstr, ia5str, hash;
+
+#if 0
+    mOSSL = rb_define_module("OpenSSL");
+    eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError);
+    mX509 = rb_define_module_under(mOSSL, "X509");
+#endif
+
+    id_aref = rb_intern("[]");
+    eX509NameError = rb_define_class_under(mX509, "NameError", eOSSLError);
+    cX509Name = rb_define_class_under(mX509, "Name", rb_cObject);
+
+    rb_include_module(cX509Name, rb_mComparable);
+
+    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);
+    rb_define_method(cX509Name, "cmp", ossl_x509name_cmp, 1);
+    rb_define_alias(cX509Name, "<=>", "cmp");
+    rb_define_method(cX509Name, "eql?", ossl_x509name_eql, 1);
+    rb_define_method(cX509Name, "hash", ossl_x509name_hash, 0);
+#ifdef HAVE_X509_NAME_HASH_OLD
+    rb_define_method(cX509Name, "hash_old", ossl_x509name_hash_old, 0);
+#endif
+    rb_define_method(cX509Name, "to_der", ossl_x509name_to_der, 0);
+
+    utf8str = INT2NUM(V_ASN1_UTF8STRING);
+    ptrstr = INT2NUM(V_ASN1_PRINTABLESTRING);
+    ia5str = INT2NUM(V_ASN1_IA5STRING);
+
+    /*
+     * The default object type for name entries.
+     */
+    rb_define_const(cX509Name, "DEFAULT_OBJECT_TYPE", utf8str);
+    hash = rb_hash_new();
+    RHASH_SET_IFNONE(hash, utf8str);
+    rb_hash_aset(hash, rb_str_new2("C"), ptrstr);
+    rb_hash_aset(hash, rb_str_new2("countryName"), ptrstr);
+    rb_hash_aset(hash, rb_str_new2("serialNumber"), ptrstr);
+    rb_hash_aset(hash, rb_str_new2("dnQualifier"), ptrstr);
+    rb_hash_aset(hash, rb_str_new2("DC"), ia5str);
+    rb_hash_aset(hash, rb_str_new2("domainComponent"), ia5str);
+    rb_hash_aset(hash, rb_str_new2("emailAddress"), ia5str);
+
+    /*
+     * The default object type template for name entries.
+     */
+    rb_define_const(cX509Name, "OBJECT_TYPE_TEMPLATE", hash);
+
+    /*
+     * A flag for #to_s.
+     *
+     * Breaks the name returned into multiple lines if longer than 80
+     * characters.
+     */
+    rb_define_const(cX509Name, "COMPAT", ULONG2NUM(XN_FLAG_COMPAT));
+
+    /*
+     * A flag for #to_s.
+     *
+     * Returns an RFC2253 format name.
+     */
+    rb_define_const(cX509Name, "RFC2253", ULONG2NUM(XN_FLAG_RFC2253));
+
+    /*
+     * A flag for #to_s.
+     *
+     * Returns a more readable format than RFC2253.
+     */
+    rb_define_const(cX509Name, "ONELINE", ULONG2NUM(XN_FLAG_ONELINE));
+
+    /*
+     * A flag for #to_s.
+     *
+     * Returns a multiline format.
+     */
+    rb_define_const(cX509Name, "MULTILINE", ULONG2NUM(XN_FLAG_MULTILINE));
+}
diff --git a/ext/openssl/ossl_x509req.c b/ext/openssl/ossl_x509req.c
new file mode 100644
index 0000000..220d2f4
--- /dev/null
+++ b/ext/openssl/ossl_x509req.c
@@ -0,0 +1,478 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2001-2002  Michal Rokos <m.rokos at sh.cvut.cz>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#include "ossl.h"
+
+#define NewX509Req(klass) \
+    TypedData_Wrap_Struct((klass), &ossl_x509req_type, 0)
+#define SetX509Req(obj, req) do { \
+    if (!(req)) { \
+	ossl_raise(rb_eRuntimeError, "Req wasn't initialized!"); \
+    } \
+    RTYPEDDATA_DATA(obj) = (req); \
+} while (0)
+#define GetX509Req(obj, req) do { \
+    TypedData_Get_Struct((obj), X509_REQ, &ossl_x509req_type, (req)); \
+    if (!(req)) { \
+	ossl_raise(rb_eRuntimeError, "Req wasn't initialized!"); \
+    } \
+} while (0)
+#define SafeGetX509Req(obj, req) do { \
+    OSSL_Check_Kind((obj), cX509Req); \
+    GetX509Req((obj), (req)); \
+} while (0)
+
+/*
+ * Classes
+ */
+VALUE cX509Req;
+VALUE eX509ReqError;
+
+static void
+ossl_x509req_free(void *ptr)
+{
+    X509_REQ_free(ptr);
+}
+
+static const rb_data_type_t ossl_x509req_type = {
+    "OpenSSL/X509/REQ",
+    {
+	0, ossl_x509req_free,
+    },
+    0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
+};
+
+/*
+ * Public functions
+ */
+VALUE
+ossl_x509req_new(X509_REQ *req)
+{
+    X509_REQ *new;
+    VALUE obj;
+
+    obj = NewX509Req(cX509Req);
+    if (!req) {
+	new = X509_REQ_new();
+    } else {
+	new = X509_REQ_dup(req);
+    }
+    if (!new) {
+	ossl_raise(eX509ReqError, NULL);
+    }
+    SetX509Req(obj, new);
+
+    return obj;
+}
+
+X509_REQ *
+GetX509ReqPtr(VALUE obj)
+{
+    X509_REQ *req;
+
+    SafeGetX509Req(obj, req);
+
+    return req;
+}
+
+X509_REQ *
+DupX509ReqPtr(VALUE obj)
+{
+    X509_REQ *req, *new;
+
+    SafeGetX509Req(obj, req);
+    if (!(new = X509_REQ_dup(req))) {
+	ossl_raise(eX509ReqError, NULL);
+    }
+
+    return new;
+}
+
+/*
+ * Private functions
+ */
+static VALUE
+ossl_x509req_alloc(VALUE klass)
+{
+    X509_REQ *req;
+    VALUE obj;
+
+    obj = NewX509Req(klass);
+    if (!(req = X509_REQ_new())) {
+	ossl_raise(eX509ReqError, NULL);
+    }
+    SetX509Req(obj, req);
+
+    return obj;
+}
+
+static VALUE
+ossl_x509req_initialize(int argc, VALUE *argv, VALUE self)
+{
+    BIO *in;
+    X509_REQ *req, *x = DATA_PTR(self);
+    VALUE arg;
+
+    if (rb_scan_args(argc, argv, "01", &arg) == 0) {
+	return self;
+    }
+    arg = ossl_to_der_if_possible(arg);
+    in = ossl_obj2bio(arg);
+    req = PEM_read_bio_X509_REQ(in, &x, NULL, NULL);
+    DATA_PTR(self) = x;
+    if (!req) {
+	OSSL_BIO_reset(in);
+	req = d2i_X509_REQ_bio(in, &x);
+	DATA_PTR(self) = x;
+    }
+    BIO_free(in);
+    if (!req) ossl_raise(eX509ReqError, NULL);
+
+    return self;
+}
+
+static VALUE
+ossl_x509req_copy(VALUE self, VALUE other)
+{
+    X509_REQ *a, *b, *req;
+
+    rb_check_frozen(self);
+    if (self == other) return self;
+    GetX509Req(self, a);
+    SafeGetX509Req(other, b);
+    if (!(req = X509_REQ_dup(b))) {
+	ossl_raise(eX509ReqError, NULL);
+    }
+    X509_REQ_free(a);
+    DATA_PTR(self) = req;
+
+    return self;
+}
+
+static VALUE
+ossl_x509req_to_pem(VALUE self)
+{
+    X509_REQ *req;
+    BIO *out;
+
+    GetX509Req(self, req);
+    if (!(out = BIO_new(BIO_s_mem()))) {
+	ossl_raise(eX509ReqError, NULL);
+    }
+    if (!PEM_write_bio_X509_REQ(out, req)) {
+	BIO_free(out);
+	ossl_raise(eX509ReqError, NULL);
+    }
+
+    return ossl_membio2str(out);
+}
+
+static VALUE
+ossl_x509req_to_der(VALUE self)
+{
+    X509_REQ *req;
+    VALUE str;
+    long len;
+    unsigned char *p;
+
+    GetX509Req(self, req);
+    if ((len = i2d_X509_REQ(req, NULL)) <= 0)
+	ossl_raise(eX509ReqError, NULL);
+    str = rb_str_new(0, len);
+    p = (unsigned char *)RSTRING_PTR(str);
+    if (i2d_X509_REQ(req, &p) <= 0)
+	ossl_raise(eX509ReqError, NULL);
+    ossl_str_adjust(str, p);
+
+    return str;
+}
+
+static VALUE
+ossl_x509req_to_text(VALUE self)
+{
+    X509_REQ *req;
+    BIO *out;
+
+    GetX509Req(self, req);
+    if (!(out = BIO_new(BIO_s_mem()))) {
+	ossl_raise(eX509ReqError, NULL);
+    }
+    if (!X509_REQ_print(out, req)) {
+	BIO_free(out);
+	ossl_raise(eX509ReqError, NULL);
+    }
+
+    return ossl_membio2str(out);
+}
+
+#if 0
+/*
+ * Makes X509 from X509_REQuest
+ */
+static VALUE
+ossl_x509req_to_x509(VALUE self, VALUE days, VALUE key)
+{
+    X509_REQ *req;
+    X509 *x509;
+
+    GetX509Req(self, req);
+    ...
+    if (!(x509 = X509_REQ_to_X509(req, d, pkey))) {
+	ossl_raise(eX509ReqError, NULL);
+    }
+
+    return ossl_x509_new(x509);
+}
+#endif
+
+static VALUE
+ossl_x509req_get_version(VALUE self)
+{
+    X509_REQ *req;
+    long version;
+
+    GetX509Req(self, req);
+    version = X509_REQ_get_version(req);
+
+    return LONG2NUM(version);
+}
+
+static VALUE
+ossl_x509req_set_version(VALUE self, VALUE version)
+{
+    X509_REQ *req;
+    long ver;
+
+    if ((ver = NUM2LONG(version)) < 0) {
+	ossl_raise(eX509ReqError, "version must be >= 0!");
+    }
+    GetX509Req(self, req);
+    if (!X509_REQ_set_version(req, ver)) {
+	ossl_raise(eX509ReqError, "X509_REQ_set_version");
+    }
+
+    return version;
+}
+
+static VALUE
+ossl_x509req_get_subject(VALUE self)
+{
+    X509_REQ *req;
+    X509_NAME *name;
+
+    GetX509Req(self, req);
+    if (!(name = X509_REQ_get_subject_name(req))) { /* NO DUP - don't free */
+	ossl_raise(eX509ReqError, NULL);
+    }
+
+    return ossl_x509name_new(name);
+}
+
+static VALUE
+ossl_x509req_set_subject(VALUE self, VALUE subject)
+{
+    X509_REQ *req;
+
+    GetX509Req(self, req);
+    /* DUPs name */
+    if (!X509_REQ_set_subject_name(req, GetX509NamePtr(subject))) {
+	ossl_raise(eX509ReqError, NULL);
+    }
+
+    return subject;
+}
+
+static VALUE
+ossl_x509req_get_signature_algorithm(VALUE self)
+{
+    X509_REQ *req;
+    const X509_ALGOR *alg;
+    BIO *out;
+
+    GetX509Req(self, req);
+
+    if (!(out = BIO_new(BIO_s_mem()))) {
+	ossl_raise(eX509ReqError, NULL);
+    }
+    X509_REQ_get0_signature(req, NULL, &alg);
+    if (!i2a_ASN1_OBJECT(out, alg->algorithm)) {
+	BIO_free(out);
+	ossl_raise(eX509ReqError, NULL);
+    }
+
+    return ossl_membio2str(out);
+}
+
+static VALUE
+ossl_x509req_get_public_key(VALUE self)
+{
+    X509_REQ *req;
+    EVP_PKEY *pkey;
+
+    GetX509Req(self, req);
+    if (!(pkey = X509_REQ_get_pubkey(req))) { /* adds reference */
+	ossl_raise(eX509ReqError, NULL);
+    }
+
+    return ossl_pkey_new(pkey); /* NO DUP - OK */
+}
+
+static VALUE
+ossl_x509req_set_public_key(VALUE self, VALUE key)
+{
+    X509_REQ *req;
+    EVP_PKEY *pkey;
+
+    GetX509Req(self, req);
+    pkey = GetPKeyPtr(key); /* NO NEED TO DUP */
+    if (!X509_REQ_set_pubkey(req, pkey)) {
+	ossl_raise(eX509ReqError, NULL);
+    }
+
+    return key;
+}
+
+static VALUE
+ossl_x509req_sign(VALUE self, VALUE key, VALUE digest)
+{
+    X509_REQ *req;
+    EVP_PKEY *pkey;
+    const EVP_MD *md;
+
+    GetX509Req(self, req);
+    pkey = GetPrivPKeyPtr(key); /* NO NEED TO DUP */
+    md = GetDigestPtr(digest);
+    if (!X509_REQ_sign(req, pkey, md)) {
+	ossl_raise(eX509ReqError, NULL);
+    }
+
+    return self;
+}
+
+/*
+ * Checks that cert signature is made with PRIVversion of this PUBLIC 'key'
+ */
+static VALUE
+ossl_x509req_verify(VALUE self, VALUE key)
+{
+    X509_REQ *req;
+    EVP_PKEY *pkey;
+
+    GetX509Req(self, req);
+    pkey = GetPKeyPtr(key); /* NO NEED TO DUP */
+    switch (X509_REQ_verify(req, pkey)) {
+      case 1:
+	return Qtrue;
+      case 0:
+	ossl_clear_error();
+	return Qfalse;
+      default:
+	ossl_raise(eX509ReqError, NULL);
+    }
+}
+
+static VALUE
+ossl_x509req_get_attributes(VALUE self)
+{
+    X509_REQ *req;
+    int count, i;
+    X509_ATTRIBUTE *attr;
+    VALUE ary;
+
+    GetX509Req(self, req);
+
+    count = X509_REQ_get_attr_count(req);
+    if (count < 0) {
+	OSSL_Debug("count < 0???");
+	return rb_ary_new();
+    }
+    ary = rb_ary_new2(count);
+    for (i=0; i<count; i++) {
+	attr = X509_REQ_get_attr(req, i);
+	rb_ary_push(ary, ossl_x509attr_new(attr));
+    }
+
+    return ary;
+}
+
+static VALUE
+ossl_x509req_set_attributes(VALUE self, VALUE ary)
+{
+    X509_REQ *req;
+    X509_ATTRIBUTE *attr;
+    long i;
+    VALUE item;
+
+    Check_Type(ary, T_ARRAY);
+    for (i=0;i<RARRAY_LEN(ary); i++) {
+	OSSL_Check_Kind(RARRAY_AREF(ary, i), cX509Attr);
+    }
+    GetX509Req(self, req);
+    while ((attr = X509_REQ_delete_attr(req, 0)))
+	X509_ATTRIBUTE_free(attr);
+    for (i=0;i<RARRAY_LEN(ary); i++) {
+	item = RARRAY_AREF(ary, i);
+	attr = GetX509AttrPtr(item);
+	if (!X509_REQ_add1_attr(req, attr)) {
+	    ossl_raise(eX509ReqError, NULL);
+	}
+    }
+    return ary;
+}
+
+static VALUE
+ossl_x509req_add_attribute(VALUE self, VALUE attr)
+{
+    X509_REQ *req;
+
+    GetX509Req(self, req);
+    if (!X509_REQ_add1_attr(req, GetX509AttrPtr(attr))) {
+	ossl_raise(eX509ReqError, NULL);
+    }
+
+    return attr;
+}
+
+/*
+ * X509_REQUEST init
+ */
+void
+Init_ossl_x509req(void)
+{
+#if 0
+    mOSSL = rb_define_module("OpenSSL");
+    eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError);
+    mX509 = rb_define_module_under(mOSSL, "X509");
+#endif
+
+    eX509ReqError = rb_define_class_under(mX509, "RequestError", eOSSLError);
+
+    cX509Req = rb_define_class_under(mX509, "Request", rb_cObject);
+
+    rb_define_alloc_func(cX509Req, ossl_x509req_alloc);
+    rb_define_method(cX509Req, "initialize", ossl_x509req_initialize, -1);
+    rb_define_copy_func(cX509Req, ossl_x509req_copy);
+
+    rb_define_method(cX509Req, "to_pem", ossl_x509req_to_pem, 0);
+    rb_define_method(cX509Req, "to_der", ossl_x509req_to_der, 0);
+    rb_define_alias(cX509Req, "to_s", "to_pem");
+    rb_define_method(cX509Req, "to_text", ossl_x509req_to_text, 0);
+    rb_define_method(cX509Req, "version", ossl_x509req_get_version, 0);
+    rb_define_method(cX509Req, "version=", ossl_x509req_set_version, 1);
+    rb_define_method(cX509Req, "subject", ossl_x509req_get_subject, 0);
+    rb_define_method(cX509Req, "subject=", ossl_x509req_set_subject, 1);
+    rb_define_method(cX509Req, "signature_algorithm", ossl_x509req_get_signature_algorithm, 0);
+    rb_define_method(cX509Req, "public_key", ossl_x509req_get_public_key, 0);
+    rb_define_method(cX509Req, "public_key=", ossl_x509req_set_public_key, 1);
+    rb_define_method(cX509Req, "sign", ossl_x509req_sign, 2);
+    rb_define_method(cX509Req, "verify", ossl_x509req_verify, 1);
+    rb_define_method(cX509Req, "attributes", ossl_x509req_get_attributes, 0);
+    rb_define_method(cX509Req, "attributes=", ossl_x509req_set_attributes, 1);
+    rb_define_method(cX509Req, "add_attribute", ossl_x509req_add_attribute, 1);
+}
diff --git a/ext/openssl/ossl_x509revoked.c b/ext/openssl/ossl_x509revoked.c
new file mode 100644
index 0000000..7960ea3
--- /dev/null
+++ b/ext/openssl/ossl_x509revoked.c
@@ -0,0 +1,279 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2001-2002  Michal Rokos <m.rokos at sh.cvut.cz>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#include "ossl.h"
+
+#define NewX509Rev(klass) \
+    TypedData_Wrap_Struct((klass), &ossl_x509rev_type, 0)
+#define SetX509Rev(obj, rev) do { \
+    if (!(rev)) { \
+	ossl_raise(rb_eRuntimeError, "REV wasn't initialized!"); \
+    } \
+    RTYPEDDATA_DATA(obj) = (rev); \
+} while (0)
+#define GetX509Rev(obj, rev) do { \
+    TypedData_Get_Struct((obj), X509_REVOKED, &ossl_x509rev_type, (rev)); \
+    if (!(rev)) { \
+	ossl_raise(rb_eRuntimeError, "REV wasn't initialized!"); \
+    } \
+} while (0)
+#define SafeGetX509Rev(obj, rev) do { \
+    OSSL_Check_Kind((obj), cX509Rev); \
+    GetX509Rev((obj), (rev)); \
+} while (0)
+
+/*
+ * Classes
+ */
+VALUE cX509Rev;
+VALUE eX509RevError;
+
+static void
+ossl_x509rev_free(void *ptr)
+{
+    X509_REVOKED_free(ptr);
+}
+
+static const rb_data_type_t ossl_x509rev_type = {
+    "OpenSSL/X509/REV",
+    {
+	0, ossl_x509rev_free,
+    },
+    0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
+};
+
+/*
+ * PUBLIC
+ */
+VALUE
+ossl_x509revoked_new(X509_REVOKED *rev)
+{
+    X509_REVOKED *new;
+    VALUE obj;
+
+    obj = NewX509Rev(cX509Rev);
+    if (!rev) {
+	new = X509_REVOKED_new();
+    } else {
+	new = X509_REVOKED_dup(rev);
+    }
+    if (!new) {
+	ossl_raise(eX509RevError, NULL);
+    }
+    SetX509Rev(obj, new);
+
+    return obj;
+}
+
+X509_REVOKED *
+DupX509RevokedPtr(VALUE obj)
+{
+    X509_REVOKED *rev, *new;
+
+    SafeGetX509Rev(obj, rev);
+    if (!(new = X509_REVOKED_dup(rev))) {
+	ossl_raise(eX509RevError, NULL);
+    }
+
+    return new;
+}
+
+/*
+ * PRIVATE
+ */
+static VALUE
+ossl_x509revoked_alloc(VALUE klass)
+{
+    X509_REVOKED *rev;
+    VALUE obj;
+
+    obj = NewX509Rev(klass);
+    if (!(rev = X509_REVOKED_new())) {
+	ossl_raise(eX509RevError, NULL);
+    }
+    SetX509Rev(obj, rev);
+
+    return obj;
+}
+
+static VALUE
+ossl_x509revoked_initialize(int argc, VALUE *argv, VALUE self)
+{
+    /* EMPTY */
+    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)
+{
+    X509_REVOKED *rev;
+
+    GetX509Rev(self, rev);
+
+    return asn1integer_to_num(X509_REVOKED_get0_serialNumber(rev));
+}
+
+static VALUE
+ossl_x509revoked_set_serial(VALUE self, VALUE num)
+{
+    X509_REVOKED *rev;
+    ASN1_INTEGER *asn1int;
+
+    GetX509Rev(self, rev);
+    asn1int = num_to_asn1integer(num, NULL);
+    if (!X509_REVOKED_set_serialNumber(rev, asn1int)) {
+	ASN1_INTEGER_free(asn1int);
+	ossl_raise(eX509RevError, "X509_REVOKED_set_serialNumber");
+    }
+    ASN1_INTEGER_free(asn1int);
+
+    return num;
+}
+
+static VALUE
+ossl_x509revoked_get_time(VALUE self)
+{
+    X509_REVOKED *rev;
+
+    GetX509Rev(self, rev);
+
+    return asn1time_to_time(X509_REVOKED_get0_revocationDate(rev));
+}
+
+static VALUE
+ossl_x509revoked_set_time(VALUE self, VALUE time)
+{
+    X509_REVOKED *rev;
+    ASN1_TIME *asn1time;
+
+    GetX509Rev(self, rev);
+    asn1time = ossl_x509_time_adjust(NULL, time);
+    if (!X509_REVOKED_set_revocationDate(rev, asn1time)) {
+	ASN1_TIME_free(asn1time);
+	ossl_raise(eX509RevError, "X509_REVOKED_set_revocationDate");
+    }
+    ASN1_TIME_free(asn1time);
+
+    return time;
+}
+/*
+ * Gets X509v3 extensions as array of X509Ext objects
+ */
+static VALUE
+ossl_x509revoked_get_extensions(VALUE self)
+{
+    X509_REVOKED *rev;
+    int count, i;
+    X509_EXTENSION *ext;
+    VALUE ary;
+
+    GetX509Rev(self, rev);
+    count = X509_REVOKED_get_ext_count(rev);
+    if (count < 0) {
+	OSSL_Debug("count < 0???");
+	return rb_ary_new();
+    }
+    ary = rb_ary_new2(count);
+    for (i=0; i<count; i++) {
+	ext = X509_REVOKED_get_ext(rev, i);
+	rb_ary_push(ary, ossl_x509ext_new(ext));
+    }
+
+    return ary;
+}
+
+/*
+ * Sets X509_EXTENSIONs
+ */
+static VALUE
+ossl_x509revoked_set_extensions(VALUE self, VALUE ary)
+{
+    X509_REVOKED *rev;
+    X509_EXTENSION *ext;
+    long i;
+    VALUE item;
+
+    Check_Type(ary, T_ARRAY);
+    for (i=0; i<RARRAY_LEN(ary); i++) {
+	OSSL_Check_Kind(RARRAY_AREF(ary, i), cX509Ext);
+    }
+    GetX509Rev(self, rev);
+    while ((ext = X509_REVOKED_delete_ext(rev, 0)))
+	X509_EXTENSION_free(ext);
+    for (i=0; i<RARRAY_LEN(ary); i++) {
+	item = RARRAY_AREF(ary, i);
+	ext = GetX509ExtPtr(item);
+	if(!X509_REVOKED_add_ext(rev, ext, -1)) {
+	    ossl_raise(eX509RevError, NULL);
+	}
+    }
+
+    return ary;
+}
+
+static VALUE
+ossl_x509revoked_add_extension(VALUE self, VALUE ext)
+{
+    X509_REVOKED *rev;
+
+    GetX509Rev(self, rev);
+    if (!X509_REVOKED_add_ext(rev, GetX509ExtPtr(ext), -1)) {
+	ossl_raise(eX509RevError, NULL);
+    }
+
+    return ext;
+}
+
+/*
+ * INIT
+ */
+void
+Init_ossl_x509revoked(void)
+{
+#if 0
+    mOSSL = rb_define_module("OpenSSL");
+    eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError);
+    mX509 = rb_define_module_under(mOSSL, "X509");
+#endif
+
+    eX509RevError = rb_define_class_under(mX509, "RevokedError", eOSSLError);
+
+    cX509Rev = rb_define_class_under(mX509, "Revoked", rb_cObject);
+
+    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);
+    rb_define_method(cX509Rev, "time", ossl_x509revoked_get_time, 0);
+    rb_define_method(cX509Rev, "time=", ossl_x509revoked_set_time, 1);
+    rb_define_method(cX509Rev, "extensions", ossl_x509revoked_get_extensions, 0);
+    rb_define_method(cX509Rev, "extensions=", ossl_x509revoked_set_extensions, 1);
+    rb_define_method(cX509Rev, "add_extension", ossl_x509revoked_add_extension, 1);
+}
diff --git a/ext/openssl/ossl_x509store.c b/ext/openssl/ossl_x509store.c
new file mode 100644
index 0000000..4becc8e
--- /dev/null
+++ b/ext/openssl/ossl_x509store.c
@@ -0,0 +1,913 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2001-2002  Michal Rokos <m.rokos at sh.cvut.cz>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#include "ossl.h"
+
+#define NewX509Store(klass) \
+    TypedData_Wrap_Struct((klass), &ossl_x509store_type, 0)
+#define SetX509Store(obj, st) do { \
+    if (!(st)) { \
+	ossl_raise(rb_eRuntimeError, "STORE wasn't initialized!"); \
+    } \
+    RTYPEDDATA_DATA(obj) = (st); \
+} while (0)
+#define GetX509Store(obj, st) do { \
+    TypedData_Get_Struct((obj), X509_STORE, &ossl_x509store_type, (st)); \
+    if (!(st)) { \
+	ossl_raise(rb_eRuntimeError, "STORE wasn't initialized!"); \
+    } \
+} while (0)
+#define SafeGetX509Store(obj, st) do { \
+    OSSL_Check_Kind((obj), cX509Store); \
+    GetX509Store((obj), (st)); \
+} while (0)
+
+#define NewX509StCtx(klass) \
+    TypedData_Wrap_Struct((klass), &ossl_x509stctx_type, 0)
+#define SetX509StCtx(obj, ctx) do { \
+    if (!(ctx)) { \
+	ossl_raise(rb_eRuntimeError, "STORE_CTX wasn't initialized!"); \
+    } \
+    RTYPEDDATA_DATA(obj) = (ctx); \
+} while (0)
+#define GetX509StCtx(obj, ctx) do { \
+    TypedData_Get_Struct((obj), X509_STORE_CTX, &ossl_x509stctx_type, (ctx)); \
+    if (!(ctx)) { \
+	ossl_raise(rb_eRuntimeError, "STORE_CTX is out of scope!"); \
+    } \
+} while (0)
+#define SafeGetX509StCtx(obj, storep) do { \
+    OSSL_Check_Kind((obj), cX509StoreContext); \
+    GetX509Store((obj), (ctx)); \
+} while (0)
+
+/*
+ * Verify callback stuff
+ */
+static int stctx_ex_verify_cb_idx, store_ex_verify_cb_idx;
+static VALUE ossl_x509stctx_new(X509_STORE_CTX *);
+
+struct ossl_verify_cb_args {
+    VALUE proc;
+    VALUE preverify_ok;
+    VALUE store_ctx;
+};
+
+static VALUE
+call_verify_cb_proc(struct ossl_verify_cb_args *args)
+{
+    return rb_funcall(args->proc, rb_intern("call"), 2,
+		      args->preverify_ok, args->store_ctx);
+}
+
+int
+ossl_verify_cb_call(VALUE proc, int ok, X509_STORE_CTX *ctx)
+{
+    VALUE rctx, ret;
+    struct ossl_verify_cb_args args;
+    int state;
+
+    if (NIL_P(proc))
+	return ok;
+
+    ret = Qfalse;
+    rctx = rb_protect((VALUE(*)(VALUE))ossl_x509stctx_new, (VALUE)ctx, &state);
+    if (state) {
+	rb_set_errinfo(Qnil);
+	rb_warn("StoreContext initialization failure");
+    }
+    else {
+	args.proc = proc;
+	args.preverify_ok = ok ? Qtrue : Qfalse;
+	args.store_ctx = rctx;
+	ret = rb_protect((VALUE(*)(VALUE))call_verify_cb_proc, (VALUE)&args, &state);
+	if (state) {
+	    rb_set_errinfo(Qnil);
+	    rb_warn("exception in verify_callback is ignored");
+	}
+	RTYPEDDATA_DATA(rctx) = NULL;
+    }
+    if (ret == Qtrue) {
+	X509_STORE_CTX_set_error(ctx, X509_V_OK);
+	ok = 1;
+    }
+    else {
+	if (X509_STORE_CTX_get_error(ctx) == X509_V_OK)
+	    X509_STORE_CTX_set_error(ctx, X509_V_ERR_CERT_REJECTED);
+	ok = 0;
+    }
+
+    return ok;
+}
+
+/*
+ * Classes
+ */
+VALUE cX509Store;
+VALUE cX509StoreContext;
+VALUE eX509StoreError;
+
+static void
+ossl_x509store_free(void *ptr)
+{
+    X509_STORE_free(ptr);
+}
+
+static const rb_data_type_t ossl_x509store_type = {
+    "OpenSSL/X509/STORE",
+    {
+	0, ossl_x509store_free,
+    },
+    0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
+};
+
+/*
+ * Public functions
+ */
+VALUE
+ossl_x509store_new(X509_STORE *store)
+{
+    VALUE obj;
+
+    obj = NewX509Store(cX509Store);
+    SetX509Store(obj, store);
+
+    return obj;
+}
+
+X509_STORE *
+GetX509StorePtr(VALUE obj)
+{
+    X509_STORE *store;
+
+    SafeGetX509Store(obj, store);
+
+    return store;
+}
+
+X509_STORE *
+DupX509StorePtr(VALUE obj)
+{
+    X509_STORE *store;
+
+    SafeGetX509Store(obj, store);
+    X509_STORE_up_ref(store);
+
+    return store;
+}
+
+/*
+ * Private functions
+ */
+static int
+x509store_verify_cb(int ok, X509_STORE_CTX *ctx)
+{
+    VALUE proc;
+
+    proc = (VALUE)X509_STORE_CTX_get_ex_data(ctx, stctx_ex_verify_cb_idx);
+    if (!proc)
+	proc = (VALUE)X509_STORE_get_ex_data(X509_STORE_CTX_get0_store(ctx),
+					     store_ex_verify_cb_idx);
+    if (!proc)
+	return ok;
+
+    return ossl_verify_cb_call(proc, ok, ctx);
+}
+
+static VALUE
+ossl_x509store_alloc(VALUE klass)
+{
+    X509_STORE *store;
+    VALUE obj;
+
+    obj = NewX509Store(klass);
+    if((store = X509_STORE_new()) == NULL){
+        ossl_raise(eX509StoreError, NULL);
+    }
+    SetX509Store(obj, store);
+
+    return obj;
+}
+
+/*
+ * General callback for OpenSSL verify
+ */
+static VALUE
+ossl_x509store_set_vfy_cb(VALUE self, VALUE cb)
+{
+    X509_STORE *store;
+
+    GetX509Store(self, store);
+    X509_STORE_set_ex_data(store, store_ex_verify_cb_idx, (void *)cb);
+    rb_iv_set(self, "@verify_callback", cb);
+
+    return cb;
+}
+
+
+/*
+ * call-seq:
+ *    X509::Store.new => store
+ *
+ * Creates a new X509::Store.
+ */
+static VALUE
+ossl_x509store_initialize(int argc, VALUE *argv, VALUE self)
+{
+    X509_STORE *store;
+
+/* BUG: This method takes any number of arguments but appears to ignore them. */
+    GetX509Store(self, store);
+#if !defined(HAVE_OPAQUE_OPENSSL)
+    /* [Bug #405] [Bug #1678] [Bug #3000]; already fixed? */
+    store->ex_data.sk = NULL;
+#endif
+    X509_STORE_set_verify_cb(store, x509store_verify_cb);
+    ossl_x509store_set_vfy_cb(self, Qnil);
+
+    /* last verification status */
+    rb_iv_set(self, "@error", Qnil);
+    rb_iv_set(self, "@error_string", Qnil);
+    rb_iv_set(self, "@chain", Qnil);
+    rb_iv_set(self, "@time", Qnil);
+
+    return self;
+}
+
+/*
+ * call-seq:
+ *   store.flags = flag
+ *
+ * Sets +flag+ to the Store. +flag+ consists of zero or more of the constants
+ * defined in with name V_FLAG_* or'ed together.
+ */
+static VALUE
+ossl_x509store_set_flags(VALUE self, VALUE flags)
+{
+    X509_STORE *store;
+    long f = NUM2LONG(flags);
+
+    GetX509Store(self, store);
+    X509_STORE_set_flags(store, f);
+
+    return flags;
+}
+
+/*
+ * call-seq:
+ *   store.purpose = purpose
+ *
+ * Sets the store's purpose to +purpose+. If specified, the verifications on
+ * the store will check every untrusted certificate's extensions are consistent
+ * with the purpose. The purpose is specified by constants:
+ *
+ * * X509::PURPOSE_SSL_CLIENT
+ * * X509::PURPOSE_SSL_SERVER
+ * * X509::PURPOSE_NS_SSL_SERVER
+ * * X509::PURPOSE_SMIME_SIGN
+ * * X509::PURPOSE_SMIME_ENCRYPT
+ * * X509::PURPOSE_CRL_SIGN
+ * * X509::PURPOSE_ANY
+ * * X509::PURPOSE_OCSP_HELPER
+ * * X509::PURPOSE_TIMESTAMP_SIGN
+ */
+static VALUE
+ossl_x509store_set_purpose(VALUE self, VALUE purpose)
+{
+    X509_STORE *store;
+    int p = NUM2INT(purpose);
+
+    GetX509Store(self, store);
+    X509_STORE_set_purpose(store, p);
+
+    return purpose;
+}
+
+/*
+ * call-seq:
+ *   store.trust = trust
+ */
+static VALUE
+ossl_x509store_set_trust(VALUE self, VALUE trust)
+{
+    X509_STORE *store;
+    int t = NUM2INT(trust);
+
+    GetX509Store(self, store);
+    X509_STORE_set_trust(store, t);
+
+    return trust;
+}
+
+/*
+ * call-seq:
+ *   store.time = time
+ *
+ * Sets the time to be used in verifications.
+ */
+static VALUE
+ossl_x509store_set_time(VALUE self, VALUE time)
+{
+    rb_iv_set(self, "@time", time);
+    return time;
+}
+
+/*
+ * call-seq:
+ *   store.add_file(file) -> self
+ *
+ * Adds the certificates in +file+ to the certificate store.  The +file+ can
+ * contain multiple PEM-encoded certificates.
+ */
+static VALUE
+ossl_x509store_add_file(VALUE self, VALUE file)
+{
+    X509_STORE *store;
+    X509_LOOKUP *lookup;
+    char *path = NULL;
+
+    if(file != Qnil){
+	rb_check_safe_obj(file);
+	path = StringValueCStr(file);
+    }
+    GetX509Store(self, store);
+    lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
+    if(lookup == NULL) ossl_raise(eX509StoreError, NULL);
+    if(X509_LOOKUP_load_file(lookup, path, X509_FILETYPE_PEM) != 1){
+        ossl_raise(eX509StoreError, NULL);
+    }
+#if OPENSSL_VERSION_NUMBER < 0x10101000 || defined(LIBRESSL_VERSION_NUMBER)
+    /*
+     * X509_load_cert_crl_file() which is called from X509_LOOKUP_load_file()
+     * did not check the return value of X509_STORE_add_{cert,crl}(), leaking
+     * "cert already in hash table" errors on the error queue, if duplicate
+     * certificates are found. This will be fixed by OpenSSL 1.1.1.
+     */
+    ossl_clear_error();
+#endif
+
+    return self;
+}
+
+/*
+ * call-seq:
+ *   store.add_path(path) -> self
+ *
+ * Adds +path+ as the hash dir to be looked up by the store.
+ */
+static VALUE
+ossl_x509store_add_path(VALUE self, VALUE dir)
+{
+    X509_STORE *store;
+    X509_LOOKUP *lookup;
+    char *path = NULL;
+
+    if(dir != Qnil){
+	rb_check_safe_obj(dir);
+	path = StringValueCStr(dir);
+    }
+    GetX509Store(self, store);
+    lookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir());
+    if(lookup == NULL) ossl_raise(eX509StoreError, NULL);
+    if(X509_LOOKUP_add_dir(lookup, path, X509_FILETYPE_PEM) != 1){
+        ossl_raise(eX509StoreError, NULL);
+    }
+
+    return self;
+}
+
+/*
+ * call-seq:
+ *   store.set_default_paths
+ *
+ * Configures +store+ to look up CA certificates from the system default
+ * certificate store as needed basis. The location of the store can usually be
+ * determined by:
+ *
+ * * OpenSSL::X509::DEFAULT_CERT_FILE
+ * * OpenSSL::X509::DEFAULT_CERT_DIR
+ */
+static VALUE
+ossl_x509store_set_default_paths(VALUE self)
+{
+    X509_STORE *store;
+
+    GetX509Store(self, store);
+    if (X509_STORE_set_default_paths(store) != 1){
+        ossl_raise(eX509StoreError, NULL);
+    }
+
+    return Qnil;
+}
+
+/*
+ * call-seq:
+ *   store.add_cert(cert)
+ *
+ * Adds the OpenSSL::X509::Certificate +cert+ to the certificate store.
+ */
+static VALUE
+ossl_x509store_add_cert(VALUE self, VALUE arg)
+{
+    X509_STORE *store;
+    X509 *cert;
+
+    cert = GetX509CertPtr(arg); /* NO NEED TO DUP */
+    GetX509Store(self, store);
+    if (X509_STORE_add_cert(store, cert) != 1){
+        ossl_raise(eX509StoreError, NULL);
+    }
+
+    return self;
+}
+
+/*
+ * call-seq:
+ *   store.add_crl(crl) -> self
+ *
+ * Adds the OpenSSL::X509::CRL +crl+ to the store.
+ */
+static VALUE
+ossl_x509store_add_crl(VALUE self, VALUE arg)
+{
+    X509_STORE *store;
+    X509_CRL *crl;
+
+    crl = GetX509CRLPtr(arg); /* NO NEED TO DUP */
+    GetX509Store(self, store);
+    if (X509_STORE_add_crl(store, crl) != 1){
+        ossl_raise(eX509StoreError, NULL);
+    }
+
+    return self;
+}
+
+static VALUE ossl_x509stctx_get_err(VALUE);
+static VALUE ossl_x509stctx_get_err_string(VALUE);
+static VALUE ossl_x509stctx_get_chain(VALUE);
+
+/*
+ * call-seq:
+ *   store.verify(cert, chain = nil) -> true | false
+ *
+ * Performs a certificate verification on the OpenSSL::X509::Certificate +cert+.
+ *
+ * +chain+ can be an array of OpenSSL::X509::Certificate that is used to
+ * construct the certificate chain.
+ *
+ * If a block is given, it overrides the callback set by #verify_callback=.
+ *
+ * After finishing the verification, the error information can be retrieved by
+ * #error, #error_string, and the resuting complete certificate chain can be
+ * retrieved by #chain.
+ */
+static VALUE
+ossl_x509store_verify(int argc, VALUE *argv, VALUE self)
+{
+    VALUE cert, chain;
+    VALUE ctx, proc, result;
+
+    rb_scan_args(argc, argv, "11", &cert, &chain);
+    ctx = rb_funcall(cX509StoreContext, rb_intern("new"), 3, self, cert, chain);
+    proc = rb_block_given_p() ?  rb_block_proc() :
+	   rb_iv_get(self, "@verify_callback");
+    rb_iv_set(ctx, "@verify_callback", proc);
+    result = rb_funcall(ctx, rb_intern("verify"), 0);
+
+    rb_iv_set(self, "@error", ossl_x509stctx_get_err(ctx));
+    rb_iv_set(self, "@error_string", ossl_x509stctx_get_err_string(ctx));
+    rb_iv_set(self, "@chain", ossl_x509stctx_get_chain(ctx));
+
+    return result;
+}
+
+/*
+ * Public Functions
+ */
+static void ossl_x509stctx_free(void*);
+
+
+static const rb_data_type_t ossl_x509stctx_type = {
+    "OpenSSL/X509/STORE_CTX",
+    {
+	0, ossl_x509stctx_free,
+    },
+    0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
+};
+
+/*
+ * Private functions
+ */
+static void
+ossl_x509stctx_free(void *ptr)
+{
+    X509_STORE_CTX *ctx = ptr;
+    if (X509_STORE_CTX_get0_untrusted(ctx))
+	sk_X509_pop_free(X509_STORE_CTX_get0_untrusted(ctx), X509_free);
+    if (X509_STORE_CTX_get0_cert(ctx))
+	X509_free(X509_STORE_CTX_get0_cert(ctx));
+    X509_STORE_CTX_free(ctx);
+}
+
+static VALUE
+ossl_x509stctx_alloc(VALUE klass)
+{
+    X509_STORE_CTX *ctx;
+    VALUE obj;
+
+    obj = NewX509StCtx(klass);
+    if((ctx = X509_STORE_CTX_new()) == NULL){
+        ossl_raise(eX509StoreError, NULL);
+    }
+    SetX509StCtx(obj, ctx);
+
+    return obj;
+}
+
+static VALUE
+ossl_x509stctx_new(X509_STORE_CTX *ctx)
+{
+    VALUE obj;
+
+    obj = NewX509StCtx(cX509StoreContext);
+    SetX509StCtx(obj, ctx);
+
+    return obj;
+}
+
+static VALUE ossl_x509stctx_set_flags(VALUE, VALUE);
+static VALUE ossl_x509stctx_set_purpose(VALUE, VALUE);
+static VALUE ossl_x509stctx_set_trust(VALUE, VALUE);
+static VALUE ossl_x509stctx_set_time(VALUE, VALUE);
+
+/*
+ * call-seq:
+ *   StoreContext.new(store, cert = nil, chain = nil)
+ */
+static VALUE
+ossl_x509stctx_initialize(int argc, VALUE *argv, VALUE self)
+{
+    VALUE store, cert, chain, t;
+    X509_STORE_CTX *ctx;
+    X509_STORE *x509st;
+    X509 *x509 = NULL;
+    STACK_OF(X509) *x509s = NULL;
+
+    rb_scan_args(argc, argv, "12", &store, &cert, &chain);
+    GetX509StCtx(self, ctx);
+    SafeGetX509Store(store, x509st);
+    if(!NIL_P(cert)) x509 = DupX509CertPtr(cert); /* NEED TO DUP */
+    if(!NIL_P(chain)) x509s = ossl_x509_ary2sk(chain);
+    if(X509_STORE_CTX_init(ctx, x509st, x509, x509s) != 1){
+        sk_X509_pop_free(x509s, X509_free);
+        ossl_raise(eX509StoreError, NULL);
+    }
+    if (!NIL_P(t = rb_iv_get(store, "@time")))
+	ossl_x509stctx_set_time(self, t);
+    rb_iv_set(self, "@verify_callback", rb_iv_get(store, "@verify_callback"));
+    rb_iv_set(self, "@cert", cert);
+
+    return self;
+}
+
+/*
+ * call-seq:
+ *   stctx.verify -> true | false
+ */
+static VALUE
+ossl_x509stctx_verify(VALUE self)
+{
+    X509_STORE_CTX *ctx;
+
+    GetX509StCtx(self, ctx);
+    X509_STORE_CTX_set_ex_data(ctx, stctx_ex_verify_cb_idx,
+			       (void *)rb_iv_get(self, "@verify_callback"));
+
+    switch (X509_verify_cert(ctx)) {
+      case 1:
+	return Qtrue;
+      case 0:
+	ossl_clear_error();
+	return Qfalse;
+      default:
+	ossl_raise(eX509CertError, NULL);
+    }
+}
+
+/*
+ * call-seq:
+ *   stctx.chain -> Array of X509::Certificate
+ */
+static VALUE
+ossl_x509stctx_get_chain(VALUE self)
+{
+    X509_STORE_CTX *ctx;
+    STACK_OF(X509) *chain;
+    X509 *x509;
+    int i, num;
+    VALUE ary;
+
+    GetX509StCtx(self, ctx);
+    if((chain = X509_STORE_CTX_get0_chain(ctx)) == NULL){
+        return Qnil;
+    }
+    if((num = sk_X509_num(chain)) < 0){
+	OSSL_Debug("certs in chain < 0???");
+	return rb_ary_new();
+    }
+    ary = rb_ary_new2(num);
+    for(i = 0; i < num; i++) {
+	x509 = sk_X509_value(chain, i);
+	rb_ary_push(ary, ossl_x509_new(x509));
+    }
+
+    return ary;
+}
+
+/*
+ * call-seq:
+ *   stctx.error -> Integer
+ */
+static VALUE
+ossl_x509stctx_get_err(VALUE self)
+{
+    X509_STORE_CTX *ctx;
+
+    GetX509StCtx(self, ctx);
+
+    return INT2NUM(X509_STORE_CTX_get_error(ctx));
+}
+
+/*
+ * call-seq:
+ *   stctx.error = error_code
+ */
+static VALUE
+ossl_x509stctx_set_error(VALUE self, VALUE err)
+{
+    X509_STORE_CTX *ctx;
+
+    GetX509StCtx(self, ctx);
+    X509_STORE_CTX_set_error(ctx, NUM2INT(err));
+
+    return err;
+}
+
+/*
+ * call-seq:
+ *   stctx.error_string -> String
+ *
+ * Returns the error string corresponding to the error code retrieved by #error.
+ */
+static VALUE
+ossl_x509stctx_get_err_string(VALUE self)
+{
+    X509_STORE_CTX *ctx;
+    long err;
+
+    GetX509StCtx(self, ctx);
+    err = X509_STORE_CTX_get_error(ctx);
+
+    return rb_str_new2(X509_verify_cert_error_string(err));
+}
+
+/*
+ * call-seq:
+ *   stctx.error_depth -> Integer
+ */
+static VALUE
+ossl_x509stctx_get_err_depth(VALUE self)
+{
+    X509_STORE_CTX *ctx;
+
+    GetX509StCtx(self, ctx);
+
+    return INT2NUM(X509_STORE_CTX_get_error_depth(ctx));
+}
+
+/*
+ * call-seq:
+ *   stctx.current_cert -> X509::Certificate
+ */
+static VALUE
+ossl_x509stctx_get_curr_cert(VALUE self)
+{
+    X509_STORE_CTX *ctx;
+
+    GetX509StCtx(self, ctx);
+
+    return ossl_x509_new(X509_STORE_CTX_get_current_cert(ctx));
+}
+
+/*
+ * call-seq:
+ *   stctx.current_crl -> X509::CRL
+ */
+static VALUE
+ossl_x509stctx_get_curr_crl(VALUE self)
+{
+    X509_STORE_CTX *ctx;
+    X509_CRL *crl;
+
+    GetX509StCtx(self, ctx);
+    crl = X509_STORE_CTX_get0_current_crl(ctx);
+    if (!crl)
+	return Qnil;
+
+    return ossl_x509crl_new(crl);
+}
+
+/*
+ * call-seq:
+ *   stctx.flags = flags
+ *
+ * Sets the verification flags to the context. See Store#flags=.
+ */
+static VALUE
+ossl_x509stctx_set_flags(VALUE self, VALUE flags)
+{
+    X509_STORE_CTX *store;
+    long f = NUM2LONG(flags);
+
+    GetX509StCtx(self, store);
+    X509_STORE_CTX_set_flags(store, f);
+
+    return flags;
+}
+
+/*
+ * call-seq:
+ *   stctx.purpose = purpose
+ *
+ * Sets the purpose of the context. See Store#purpose=.
+ */
+static VALUE
+ossl_x509stctx_set_purpose(VALUE self, VALUE purpose)
+{
+    X509_STORE_CTX *store;
+    int p = NUM2INT(purpose);
+
+    GetX509StCtx(self, store);
+    X509_STORE_CTX_set_purpose(store, p);
+
+    return purpose;
+}
+
+/*
+ * call-seq:
+ *   stctx.trust = trust
+ */
+static VALUE
+ossl_x509stctx_set_trust(VALUE self, VALUE trust)
+{
+    X509_STORE_CTX *store;
+    int t = NUM2INT(trust);
+
+    GetX509StCtx(self, store);
+    X509_STORE_CTX_set_trust(store, t);
+
+    return trust;
+}
+
+/*
+ * call-seq:
+ *   stctx.time = time
+ *
+ * Sets the time used in the verification. If not set, the current time is used.
+ */
+static VALUE
+ossl_x509stctx_set_time(VALUE self, VALUE time)
+{
+    X509_STORE_CTX *store;
+    long t;
+
+    t = NUM2LONG(rb_Integer(time));
+    GetX509StCtx(self, store);
+    X509_STORE_CTX_set_time(store, 0, t);
+
+    return time;
+}
+
+/*
+ * INIT
+ */
+void
+Init_ossl_x509store(void)
+{
+#if 0
+    mOSSL = rb_define_module("OpenSSL");
+    eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError);
+    mX509 = rb_define_module_under(mOSSL, "X509");
+#endif
+
+    /* Register ext_data slot for verify callback Proc */
+    stctx_ex_verify_cb_idx = X509_STORE_CTX_get_ex_new_index(0, (void *)"stctx_ex_verify_cb_idx", 0, 0, 0);
+    if (stctx_ex_verify_cb_idx < 0)
+	ossl_raise(eOSSLError, "X509_STORE_CTX_get_ex_new_index");
+    store_ex_verify_cb_idx = X509_STORE_get_ex_new_index(0, (void *)"store_ex_verify_cb_idx", 0, 0, 0);
+    if (store_ex_verify_cb_idx < 0)
+	ossl_raise(eOSSLError, "X509_STORE_get_ex_new_index");
+
+    eX509StoreError = rb_define_class_under(mX509, "StoreError", eOSSLError);
+
+    /* Document-class: OpenSSL::X509::Store
+     *
+     * The X509 certificate store holds trusted CA certificates used to verify
+     * peer certificates.
+     *
+     * The easiest way to create a useful certificate store is:
+     *
+     *   cert_store = OpenSSL::X509::Store.new
+     *   cert_store.set_default_paths
+     *
+     * This will use your system's built-in certificates.
+     *
+     * If your system does not have a default set of certificates you can obtain
+     * a set extracted from Mozilla CA certificate store by cURL maintainers
+     * here: https://curl.haxx.se/docs/caextract.html (You may wish to use the
+     * firefox-db2pem.sh script to extract the certificates from a local install
+     * to avoid man-in-the-middle attacks.)
+     *
+     * After downloading or generating a cacert.pem from the above link you
+     * can create a certificate store from the pem file like this:
+     *
+     *   cert_store = OpenSSL::X509::Store.new
+     *   cert_store.add_file 'cacert.pem'
+     *
+     * The certificate store can be used with an SSLSocket like this:
+     *
+     *   ssl_context = OpenSSL::SSL::SSLContext.new
+     *   ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER
+     *   ssl_context.cert_store = cert_store
+     *
+     *   tcp_socket = TCPSocket.open 'example.com', 443
+     *
+     *   ssl_socket = OpenSSL::SSL::SSLSocket.new tcp_socket, ssl_context
+     */
+
+    cX509Store = rb_define_class_under(mX509, "Store", rb_cObject);
+    /*
+     * The callback for additional certificate verification. It is invoked for
+     * each untrusted certificate in the chain.
+     *
+     * The callback is invoked with two values, a boolean that indicates if the
+     * pre-verification by OpenSSL has succeeded or not, and the StoreContext in
+     * use. The callback must return either true or false.
+     */
+    rb_attr(cX509Store, rb_intern("verify_callback"), 1, 0, Qfalse);
+    /*
+     * The error code set by the last call of #verify.
+     */
+    rb_attr(cX509Store, rb_intern("error"), 1, 0, Qfalse);
+    /*
+     * The description for the error code set by the last call of #verify.
+     */
+    rb_attr(cX509Store, rb_intern("error_string"), 1, 0, Qfalse);
+    /*
+     * The certificate chain constructed by the last call of #verify.
+     */
+    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);
+    rb_define_method(cX509Store, "trust=",       ossl_x509store_set_trust, 1);
+    rb_define_method(cX509Store, "time=",        ossl_x509store_set_time, 1);
+    rb_define_method(cX509Store, "add_path",     ossl_x509store_add_path, 1);
+    rb_define_method(cX509Store, "add_file",     ossl_x509store_add_file, 1);
+    rb_define_method(cX509Store, "set_default_paths", ossl_x509store_set_default_paths, 0);
+    rb_define_method(cX509Store, "add_cert",     ossl_x509store_add_cert, 1);
+    rb_define_method(cX509Store, "add_crl",      ossl_x509store_add_crl, 1);
+    rb_define_method(cX509Store, "verify",       ossl_x509store_verify, -1);
+
+    /*
+     * Document-class: OpenSSL::X509::StoreContext
+     *
+     * A StoreContext is used while validating a single certificate and holds
+     * the status involved.
+     */
+    cX509StoreContext = rb_define_class_under(mX509,"StoreContext", rb_cObject);
+    rb_define_alloc_func(cX509StoreContext, ossl_x509stctx_alloc);
+    rb_define_method(cX509StoreContext, "initialize", ossl_x509stctx_initialize, -1);
+    rb_undef_method(cX509StoreContext, "initialize_copy");
+    rb_define_method(cX509StoreContext, "verify", ossl_x509stctx_verify, 0);
+    rb_define_method(cX509StoreContext, "chain", ossl_x509stctx_get_chain,0);
+    rb_define_method(cX509StoreContext, "error", ossl_x509stctx_get_err, 0);
+    rb_define_method(cX509StoreContext, "error=", ossl_x509stctx_set_error, 1);
+    rb_define_method(cX509StoreContext, "error_string", ossl_x509stctx_get_err_string,0);
+    rb_define_method(cX509StoreContext, "error_depth", ossl_x509stctx_get_err_depth, 0);
+    rb_define_method(cX509StoreContext, "current_cert", ossl_x509stctx_get_curr_cert, 0);
+    rb_define_method(cX509StoreContext, "current_crl", ossl_x509stctx_get_curr_crl, 0);
+    rb_define_method(cX509StoreContext, "flags=", ossl_x509stctx_set_flags, 1);
+    rb_define_method(cX509StoreContext, "purpose=", ossl_x509stctx_set_purpose, 1);
+    rb_define_method(cX509StoreContext, "trust=", ossl_x509stctx_set_trust, 1);
+    rb_define_method(cX509StoreContext, "time=", ossl_x509stctx_set_time, 1);
+}
diff --git a/ext/openssl/ruby_missing.h b/ext/openssl/ruby_missing.h
new file mode 100644
index 0000000..8dacc82
--- /dev/null
+++ b/ext/openssl/ruby_missing.h
@@ -0,0 +1,23 @@
+/*
+ * 'OpenSSL for Ruby' project
+ * Copyright (C) 2001-2003  Michal Rokos <m.rokos at sh.cvut.cz>
+ * All rights reserved.
+ */
+/*
+ * This program is licensed under the same licence as Ruby.
+ * (See the file 'LICENCE'.)
+ */
+#if !defined(_OSSL_RUBY_MISSING_H_)
+#define _OSSL_RUBY_MISSING_H_
+
+#define rb_define_copy_func(klass, func) \
+	rb_define_method((klass), "initialize_copy", (func), 1)
+
+#define FPTR_TO_FD(fptr) ((fptr)->fd)
+
+#ifndef RB_INTEGER_TYPE_P
+/* for Ruby 2.3 compatibility */
+#define RB_INTEGER_TYPE_P(obj) (RB_FIXNUM_P(obj) || RB_TYPE_P(obj, T_BIGNUM))
+#endif
+
+#endif /* _OSSL_RUBY_MISSING_H_ */
diff --git a/lib/openssl.rb b/lib/openssl.rb
new file mode 100644
index 0000000..26d167a
--- /dev/null
+++ b/lib/openssl.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: false
+=begin
+= Info
+  'OpenSSL for Ruby 2' project
+  Copyright (C) 2002  Michal Rokos <m.rokos at sh.cvut.cz>
+  All rights reserved.
+
+= Licence
+  This program is licensed under the same licence as Ruby.
+  (See the file 'LICENCE'.)
+=end
+
+require 'openssl.so'
+
+require 'openssl/bn'
+require 'openssl/pkey'
+require 'openssl/cipher'
+require 'openssl/config'
+require 'openssl/digest'
+require 'openssl/x509'
+require 'openssl/ssl'
diff --git a/lib/openssl/bn.rb b/lib/openssl/bn.rb
new file mode 100644
index 0000000..6d6c96e
--- /dev/null
+++ b/lib/openssl/bn.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: false
+#--
+#
+# = Ruby-space definitions that completes C-space funcs for BN
+#
+# = Info
+# 'OpenSSL for Ruby 2' project
+# Copyright (C) 2002  Michal Rokos <m.rokos at sh.cvut.cz>
+# All rights reserved.
+#
+# = Licence
+# This program is licensed under the same licence as Ruby.
+# (See the file 'LICENCE'.)
+#++
+
+module OpenSSL
+  class BN
+    include Comparable
+
+    def pretty_print(q)
+      q.object_group(self) {
+        q.text ' '
+        q.text to_i.to_s
+      }
+    end
+  end # BN
+end # OpenSSL
+
+##
+# Add double dispatch to Integer
+#
+class Integer
+  # Casts an Integer as an OpenSSL::BN
+  #
+  # See `man bn` for more info.
+  def to_bn
+    OpenSSL::BN::new(self)
+  end
+end # Integer
diff --git a/lib/openssl/buffering.rb b/lib/openssl/buffering.rb
new file mode 100644
index 0000000..b0dffef
--- /dev/null
+++ b/lib/openssl/buffering.rb
@@ -0,0 +1,459 @@
+# coding: binary
+# frozen_string_literal: false
+#--
+#= Info
+#  'OpenSSL for Ruby 2' project
+#  Copyright (C) 2001 GOTOU YUUZOU <gotoyuzo at notwork.org>
+#  All rights reserved.
+#
+#= Licence
+#  This program is licensed under the same licence as Ruby.
+#  (See the file 'LICENCE'.)
+#++
+
+##
+# OpenSSL IO buffering mix-in module.
+#
+# This module allows an OpenSSL::SSL::SSLSocket to behave like an IO.
+#
+# You typically won't use this module directly, you can see it implemented in
+# OpenSSL::SSL::SSLSocket.
+
+module OpenSSL::Buffering
+  include Enumerable
+
+  ##
+  # The "sync mode" of the SSLSocket.
+  #
+  # See IO#sync for full details.
+
+  attr_accessor :sync
+
+  ##
+  # Default size to read from or write to the SSLSocket for buffer operations.
+
+  BLOCK_SIZE = 1024*16
+
+  ##
+  # Creates an instance of OpenSSL's buffering IO module.
+
+  def initialize(*)
+    super
+    @eof = false
+    @rbuffer = ""
+    @sync = @io.sync
+  end
+
+  #
+  # for reading.
+  #
+  private
+
+  ##
+  # Fills the buffer from the underlying SSLSocket
+
+  def fill_rbuff
+    begin
+      @rbuffer << self.sysread(BLOCK_SIZE)
+    rescue Errno::EAGAIN
+      retry
+    rescue EOFError
+      @eof = true
+    end
+  end
+
+  ##
+  # Consumes +size+ bytes from the buffer
+
+  def consume_rbuff(size=nil)
+    if @rbuffer.empty?
+      nil
+    else
+      size = @rbuffer.size unless size
+      ret = @rbuffer[0, size]
+      @rbuffer[0, size] = ""
+      ret
+    end
+  end
+
+  public
+
+  ##
+  # Reads +size+ bytes from the stream.  If +buf+ is provided it must
+  # reference a string which will receive the data.
+  #
+  # See IO#read for full details.
+
+  def read(size=nil, buf=nil)
+    if size == 0
+      if buf
+        buf.clear
+        return buf
+      else
+        return ""
+      end
+    end
+    until @eof
+      break if size && size <= @rbuffer.size
+      fill_rbuff
+    end
+    ret = consume_rbuff(size) || ""
+    if buf
+      buf.replace(ret)
+      ret = buf
+    end
+    (size && ret.empty?) ? nil : ret
+  end
+
+  ##
+  # Reads at most +maxlen+ bytes from the stream.  If +buf+ is provided it
+  # must reference a string which will receive the data.
+  #
+  # See IO#readpartial for full details.
+
+  def readpartial(maxlen, buf=nil)
+    if maxlen == 0
+      if buf
+        buf.clear
+        return buf
+      else
+        return ""
+      end
+    end
+    if @rbuffer.empty?
+      begin
+        return sysread(maxlen, buf)
+      rescue Errno::EAGAIN
+        retry
+      end
+    end
+    ret = consume_rbuff(maxlen)
+    if buf
+      buf.replace(ret)
+      ret = buf
+    end
+    ret
+  end
+
+  ##
+  # Reads at most +maxlen+ bytes in the non-blocking manner.
+  #
+  # When no data can be read without blocking it raises
+  # OpenSSL::SSL::SSLError extended by IO::WaitReadable or IO::WaitWritable.
+  #
+  # IO::WaitReadable means SSL needs to read internally so read_nonblock
+  # should be called again when the underlying IO is readable.
+  #
+  # IO::WaitWritable means SSL needs to write internally so read_nonblock
+  # should be called again after the underlying IO is writable.
+  #
+  # OpenSSL::Buffering#read_nonblock needs two rescue clause as follows:
+  #
+  #   # emulates blocking read (readpartial).
+  #   begin
+  #     result = ssl.read_nonblock(maxlen)
+  #   rescue IO::WaitReadable
+  #     IO.select([io])
+  #     retry
+  #   rescue IO::WaitWritable
+  #     IO.select(nil, [io])
+  #     retry
+  #   end
+  #
+  # Note that one reason that read_nonblock writes to the underlying IO is
+  # when the peer requests a new TLS/SSL handshake.  See openssl the FAQ for
+  # more details.  http://www.openssl.org/support/faq.html
+  #
+  # By specifying `exception: false`, the options hash allows you to indicate
+  # that read_nonblock should not raise an IO::Wait*able exception, but
+  # return the symbol :wait_writable or :wait_readable instead.
+
+  def read_nonblock(maxlen, buf=nil, exception: true)
+    if maxlen == 0
+      if buf
+        buf.clear
+        return buf
+      else
+        return ""
+      end
+    end
+    if @rbuffer.empty?
+      return sysread_nonblock(maxlen, buf, exception: exception)
+    end
+    ret = consume_rbuff(maxlen)
+    if buf
+      buf.replace(ret)
+      ret = buf
+    end
+    ret
+  end
+
+  ##
+  # Reads the next "line" from the stream.  Lines are separated by +eol+.  If
+  # +limit+ is provided the result will not be longer than the given number of
+  # bytes.
+  #
+  # +eol+ may be a String or Regexp.
+  #
+  # Unlike IO#gets the line read will not be assigned to +$_+.
+  #
+  # Unlike IO#gets the separator must be provided if a limit is provided.
+
+  def gets(eol=$/, limit=nil)
+    idx = @rbuffer.index(eol)
+    until @eof
+      break if idx
+      fill_rbuff
+      idx = @rbuffer.index(eol)
+    end
+    if eol.is_a?(Regexp)
+      size = idx ? idx+$&.size : nil
+    else
+      size = idx ? idx+eol.size : nil
+    end
+    if size && limit && limit >= 0
+      size = [size, limit].min
+    end
+    consume_rbuff(size)
+  end
+
+  ##
+  # Executes the block for every line in the stream where lines are separated
+  # by +eol+.
+  #
+  # See also #gets
+
+  def each(eol=$/)
+    while line = self.gets(eol)
+      yield line
+    end
+  end
+  alias each_line each
+
+  ##
+  # Reads lines from the stream which are separated by +eol+.
+  #
+  # See also #gets
+
+  def readlines(eol=$/)
+    ary = []
+    while line = self.gets(eol)
+      ary << line
+    end
+    ary
+  end
+
+  ##
+  # Reads a line from the stream which is separated by +eol+.
+  #
+  # Raises EOFError if at end of file.
+
+  def readline(eol=$/)
+    raise EOFError if eof?
+    gets(eol)
+  end
+
+  ##
+  # Reads one character from the stream.  Returns nil if called at end of
+  # file.
+
+  def getc
+    read(1)
+  end
+
+  ##
+  # Calls the given block once for each byte in the stream.
+
+  def each_byte # :yields: byte
+    while c = getc
+      yield(c.ord)
+    end
+  end
+
+  ##
+  # Reads a one-character string from the stream.  Raises an EOFError at end
+  # of file.
+
+  def readchar
+    raise EOFError if eof?
+    getc
+  end
+
+  ##
+  # Pushes character +c+ back onto the stream such that a subsequent buffered
+  # character read will return it.
+  #
+  # Unlike IO#getc multiple bytes may be pushed back onto the stream.
+  #
+  # Has no effect on unbuffered reads (such as #sysread).
+
+  def ungetc(c)
+    @rbuffer[0,0] = c.chr
+  end
+
+  ##
+  # Returns true if the stream is at file which means there is no more data to
+  # be read.
+
+  def eof?
+    fill_rbuff if !@eof && @rbuffer.empty?
+    @eof && @rbuffer.empty?
+  end
+  alias eof eof?
+
+  #
+  # for writing.
+  #
+  private
+
+  ##
+  # Writes +s+ to the buffer.  When the buffer is full or #sync is true the
+  # buffer is flushed to the underlying socket.
+
+  def do_write(s)
+    @wbuffer = "" unless defined? @wbuffer
+    @wbuffer << s
+    @wbuffer.force_encoding(Encoding::BINARY)
+    @sync ||= false
+    if @sync or @wbuffer.size > BLOCK_SIZE or idx = @wbuffer.rindex($/)
+      remain = idx ? idx + $/.size : @wbuffer.length
+      nwritten = 0
+      while remain > 0
+        str = @wbuffer[nwritten,remain]
+        begin
+          nwrote = syswrite(str)
+        rescue Errno::EAGAIN
+          retry
+        end
+        remain -= nwrote
+        nwritten += nwrote
+      end
+      @wbuffer[0,nwritten] = ""
+    end
+  end
+
+  public
+
+  ##
+  # Writes +s+ to the stream.  If the argument is not a string it will be
+  # converted using String#to_s.  Returns the number of bytes written.
+
+  def write(s)
+    do_write(s)
+    s.bytesize
+  end
+
+  ##
+  # Writes +s+ in the non-blocking manner.
+  #
+  # If there is buffered data, it is flushed first.  This may block.
+  #
+  # write_nonblock returns number of bytes written to the SSL connection.
+  #
+  # When no data can be written without blocking it raises
+  # OpenSSL::SSL::SSLError extended by IO::WaitReadable or IO::WaitWritable.
+  #
+  # IO::WaitReadable means SSL needs to read internally so write_nonblock
+  # should be called again after the underlying IO is readable.
+  #
+  # IO::WaitWritable means SSL needs to write internally so write_nonblock
+  # should be called again after underlying IO is writable.
+  #
+  # So OpenSSL::Buffering#write_nonblock needs two rescue clause as follows.
+  #
+  #   # emulates blocking write.
+  #   begin
+  #     result = ssl.write_nonblock(str)
+  #   rescue IO::WaitReadable
+  #     IO.select([io])
+  #     retry
+  #   rescue IO::WaitWritable
+  #     IO.select(nil, [io])
+  #     retry
+  #   end
+  #
+  # Note that one reason that write_nonblock reads from the underlying IO
+  # is when the peer requests a new TLS/SSL handshake.  See the openssl FAQ
+  # for more details.  http://www.openssl.org/support/faq.html
+  #
+  # By specifying `exception: false`, the options hash allows you to indicate
+  # that write_nonblock should not raise an IO::Wait*able exception, but
+  # return the symbol :wait_writable or :wait_readable instead.
+
+  def write_nonblock(s, exception: true)
+    flush
+    syswrite_nonblock(s, exception: exception)
+  end
+
+  ##
+  # Writes +s+ to the stream.  +s+ will be converted to a String using
+  # String#to_s.
+
+  def <<(s)
+    do_write(s)
+    self
+  end
+
+  ##
+  # Writes +args+ to the stream along with a record separator.
+  #
+  # See IO#puts for full details.
+
+  def puts(*args)
+    s = ""
+    if args.empty?
+      s << "\n"
+    end
+    args.each{|arg|
+      s << arg.to_s
+      if $/ && /\n\z/ !~ s
+        s << "\n"
+      end
+    }
+    do_write(s)
+    nil
+  end
+
+  ##
+  # Writes +args+ to the stream.
+  #
+  # See IO#print for full details.
+
+  def print(*args)
+    s = ""
+    args.each{ |arg| s << arg.to_s }
+    do_write(s)
+    nil
+  end
+
+  ##
+  # Formats and writes to the stream converting parameters under control of
+  # the format string.
+  #
+  # See Kernel#sprintf for format string details.
+
+  def printf(s, *args)
+    do_write(s % args)
+    nil
+  end
+
+  ##
+  # Flushes buffered data to the SSLSocket.
+
+  def flush
+    osync = @sync
+    @sync = true
+    do_write ""
+    return self
+  ensure
+    @sync = osync
+  end
+
+  ##
+  # Closes the SSLSocket and flushes any unwritten data.
+
+  def close
+    flush rescue nil
+    sysclose
+  end
+end
diff --git a/lib/openssl/cipher.rb b/lib/openssl/cipher.rb
new file mode 100644
index 0000000..af721b3
--- /dev/null
+++ b/lib/openssl/cipher.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: false
+#--
+# = Ruby-space predefined Cipher subclasses
+#
+# = Info
+# 'OpenSSL for Ruby 2' project
+# Copyright (C) 2002  Michal Rokos <m.rokos at sh.cvut.cz>
+# All rights reserved.
+#
+# = Licence
+# This program is licensed under the same licence as Ruby.
+# (See the file 'LICENCE'.)
+#++
+
+module OpenSSL
+  class Cipher
+    %w(AES CAST5 BF DES IDEA RC2 RC4 RC5).each{|name|
+      klass = Class.new(Cipher){
+        define_method(:initialize){|*args|
+          cipher_name = args.inject(name){|n, arg| "#{n}-#{arg}" }
+          super(cipher_name.downcase)
+        }
+      }
+      const_set(name, klass)
+    }
+
+    %w(128 192 256).each{|keylen|
+      klass = Class.new(Cipher){
+        define_method(:initialize){|mode = "CBC"|
+          super("aes-#{keylen}-#{mode}".downcase)
+        }
+      }
+      const_set("AES#{keylen}", klass)
+    }
+
+    # call-seq:
+    #   cipher.random_key -> key
+    #
+    # Generate a random key with OpenSSL::Random.random_bytes and sets it to
+    # the cipher, and returns it.
+    #
+    # You must call #encrypt or #decrypt before calling this method.
+    def random_key
+      str = OpenSSL::Random.random_bytes(self.key_len)
+      self.key = str
+    end
+
+    # call-seq:
+    #   cipher.random_iv -> iv
+    #
+    # Generate a random IV with OpenSSL::Random.random_bytes and sets it to the
+    # cipher, and returns it.
+    #
+    # You must call #encrypt or #decrypt before calling this method.
+    def random_iv
+      str = OpenSSL::Random.random_bytes(self.iv_len)
+      self.iv = str
+    end
+
+    # Deprecated.
+    #
+    # This class is only provided for backwards compatibility.
+    # Use OpenSSL::Cipher.
+    class Cipher < Cipher; end
+    deprecate_constant :Cipher
+  end # Cipher
+end # OpenSSL
diff --git a/lib/openssl/config.rb b/lib/openssl/config.rb
new file mode 100644
index 0000000..8822545
--- /dev/null
+++ b/lib/openssl/config.rb
@@ -0,0 +1,473 @@
+# frozen_string_literal: false
+=begin
+= Ruby-space definitions that completes C-space funcs for Config
+
+= Info
+  Copyright (C) 2010  Hiroshi Nakamura <nahi at ruby-lang.org>
+
+= Licence
+  This program is licensed under the same licence as Ruby.
+  (See the file 'LICENCE'.)
+
+=end
+
+require 'stringio'
+
+module OpenSSL
+  ##
+  # = OpenSSL::Config
+  #
+  # Configuration for the openssl library.
+  #
+  # Many system's installation of openssl library will depend on your system
+  # configuration. See the value of OpenSSL::Config::DEFAULT_CONFIG_FILE for
+  # the location of the file for your host.
+  #
+  # See also http://www.openssl.org/docs/apps/config.html
+  class Config
+    include Enumerable
+
+    class << self
+
+      ##
+      # Parses a given +string+ as a blob that contains configuration for openssl.
+      #
+      # If the source of the IO is a file, then consider using #parse_config.
+      def parse(string)
+        c = new()
+        parse_config(StringIO.new(string)).each do |section, hash|
+          c[section] = hash
+        end
+        c
+      end
+
+      ##
+      # load is an alias to ::new
+      alias load new
+
+      ##
+      # Parses the configuration data read from +io+, see also #parse.
+      #
+      # Raises a ConfigError on invalid configuration data.
+      def parse_config(io)
+        begin
+          parse_config_lines(io)
+        rescue ConfigError => e
+          e.message.replace("error in line #{io.lineno}: " + e.message)
+          raise
+        end
+      end
+
+      def get_key_string(data, section, key) # :nodoc:
+        if v = data[section] && data[section][key]
+          return v
+        elsif section == 'ENV'
+          if v = ENV[key]
+            return v
+          end
+        end
+        if v = data['default'] && data['default'][key]
+          return v
+        end
+      end
+
+    private
+
+      def parse_config_lines(io)
+        section = 'default'
+        data = {section => {}}
+        while definition = get_definition(io)
+          definition = clear_comments(definition)
+          next if definition.empty?
+          if definition[0] == ?[
+            if /\[([^\]]*)\]/ =~ definition
+              section = $1.strip
+              data[section] ||= {}
+            else
+              raise ConfigError, "missing close square bracket"
+            end
+          else
+            if /\A([^:\s]*)(?:::([^:\s]*))?\s*=(.*)\z/ =~ definition
+              if $2
+                section = $1
+                key = $2
+              else
+                key = $1
+              end
+              value = unescape_value(data, section, $3)
+              (data[section] ||= {})[key] = value.strip
+            else
+              raise ConfigError, "missing equal sign"
+            end
+          end
+        end
+        data
+      end
+
+      # escape with backslash
+      QUOTE_REGEXP_SQ = /\A([^'\\]*(?:\\.[^'\\]*)*)'/
+      # escape with backslash and doubled dq
+      QUOTE_REGEXP_DQ = /\A([^"\\]*(?:""[^"\\]*|\\.[^"\\]*)*)"/
+      # escaped char map
+      ESCAPE_MAP = {
+        "r" => "\r",
+        "n" => "\n",
+        "b" => "\b",
+        "t" => "\t",
+      }
+
+      def unescape_value(data, section, value)
+        scanned = []
+        while m = value.match(/['"\\$]/)
+          scanned << m.pre_match
+          c = m[0]
+          value = m.post_match
+          case c
+          when "'"
+            if m = value.match(QUOTE_REGEXP_SQ)
+              scanned << m[1].gsub(/\\(.)/, '\\1')
+              value = m.post_match
+            else
+              break
+            end
+          when '"'
+            if m = value.match(QUOTE_REGEXP_DQ)
+              scanned << m[1].gsub(/""/, '').gsub(/\\(.)/, '\\1')
+              value = m.post_match
+            else
+              break
+            end
+          when "\\"
+            c = value.slice!(0, 1)
+            scanned << (ESCAPE_MAP[c] || c)
+          when "$"
+            ref, value = extract_reference(value)
+            refsec = section
+            if ref.index('::')
+              refsec, ref = ref.split('::', 2)
+            end
+            if v = get_key_string(data, refsec, ref)
+              scanned << v
+            else
+              raise ConfigError, "variable has no value"
+            end
+          else
+            raise 'must not reaced'
+          end
+        end
+        scanned << value
+        scanned.join
+      end
+
+      def extract_reference(value)
+        rest = ''
+        if m = value.match(/\(([^)]*)\)|\{([^}]*)\}/)
+          value = m[1] || m[2]
+          rest = m.post_match
+        elsif [?(, ?{].include?(value[0])
+          raise ConfigError, "no close brace"
+        end
+        if m = value.match(/[a-zA-Z0-9_]*(?:::[a-zA-Z0-9_]*)?/)
+          return m[0], m.post_match + rest
+        else
+          raise
+        end
+      end
+
+      def clear_comments(line)
+        # FCOMMENT
+        if m = line.match(/\A([\t\n\f ]*);.*\z/)
+          return m[1]
+        end
+        # COMMENT
+        scanned = []
+        while m = line.match(/[#'"\\]/)
+          scanned << m.pre_match
+          c = m[0]
+          line = m.post_match
+          case c
+          when '#'
+            line = nil
+            break
+          when "'", '"'
+            regexp = (c == "'") ? QUOTE_REGEXP_SQ : QUOTE_REGEXP_DQ
+            scanned << c
+            if m = line.match(regexp)
+              scanned << m[0]
+              line = m.post_match
+            else
+              scanned << line
+              line = nil
+              break
+            end
+          when "\\"
+            scanned << c
+            scanned << line.slice!(0, 1)
+          else
+            raise 'must not reaced'
+          end
+        end
+        scanned << line
+        scanned.join
+      end
+
+      def get_definition(io)
+        if line = get_line(io)
+          while /[^\\]\\\z/ =~ line
+            if extra = get_line(io)
+              line += extra
+            else
+              break
+            end
+          end
+          return line.strip
+        end
+      end
+
+      def get_line(io)
+        if line = io.gets
+          line.gsub(/[\r\n]*/, '')
+        end
+      end
+    end
+
+    ##
+    # Creates an instance of OpenSSL's configuration class.
+    #
+    # This can be used in contexts like OpenSSL::X509::ExtensionFactory.config=
+    #
+    # If the optional +filename+ parameter is provided, then it is read in and
+    # parsed via #parse_config.
+    #
+    # This can raise IO exceptions based on the access, or availability of the
+    # file. A ConfigError exception may be raised depending on the validity of
+    # the data being configured.
+    #
+    def initialize(filename = nil)
+      @data = {}
+      if filename
+        File.open(filename.to_s) do |file|
+          Config.parse_config(file).each do |section, hash|
+            self[section] = hash
+          end
+        end
+      end
+    end
+
+    ##
+    # Gets the value of +key+ from the given +section+
+    #
+    # Given the following configurating file being loaded:
+    #
+    #   config = OpenSSL::Config.load('foo.cnf')
+    #     #=> #<OpenSSL::Config sections=["default"]>
+    #   puts config.to_s
+    #     #=> [ default ]
+    #     #   foo=bar
+    #
+    # You can get a specific value from the config if you know the +section+
+    # and +key+ like so:
+    #
+    #   config.get_value('default','foo')
+    #     #=> "bar"
+    #
+    def get_value(section, key)
+      if section.nil?
+        raise TypeError.new('nil not allowed')
+      end
+      section = 'default' if section.empty?
+      get_key_string(section, key)
+    end
+
+    ##
+    #
+    # *Deprecated*
+    #
+    # Use #get_value instead
+    def value(arg1, arg2 = nil) # :nodoc:
+      warn('Config#value is deprecated; use Config#get_value')
+      if arg2.nil?
+        section, key = 'default', arg1
+      else
+        section, key = arg1, arg2
+      end
+      section ||= 'default'
+      section = 'default' if section.empty?
+      get_key_string(section, key)
+    end
+
+    ##
+    # Set the target +key+ with a given +value+ under a specific +section+.
+    #
+    # Given the following configurating file being loaded:
+    #
+    #   config = OpenSSL::Config.load('foo.cnf')
+    #     #=> #<OpenSSL::Config sections=["default"]>
+    #   puts config.to_s
+    #     #=> [ default ]
+    #     #   foo=bar
+    #
+    # You can set the value of +foo+ under the +default+ section to a new
+    # value:
+    #
+    #   config.add_value('default', 'foo', 'buzz')
+    #     #=> "buzz"
+    #   puts config.to_s
+    #     #=> [ default ]
+    #     #   foo=buzz
+    #
+    def add_value(section, key, value)
+      check_modify
+      (@data[section] ||= {})[key] = value
+    end
+
+    ##
+    # Get a specific +section+ from the current configuration
+    #
+    # Given the following configurating file being loaded:
+    #
+    #   config = OpenSSL::Config.load('foo.cnf')
+    #     #=> #<OpenSSL::Config sections=["default"]>
+    #   puts config.to_s
+    #     #=> [ default ]
+    #     #   foo=bar
+    #
+    # You can get a hash of the specific section like so:
+    #
+    #   config['default']
+    #     #=> {"foo"=>"bar"}
+    #
+    def [](section)
+      @data[section] || {}
+    end
+
+    ##
+    # Deprecated
+    #
+    # Use #[] instead
+    def section(name) # :nodoc:
+      warn('Config#section is deprecated; use Config#[]')
+      @data[name] || {}
+    end
+
+    ##
+    # Sets a specific +section+ name with a Hash +pairs+
+    #
+    # Given the following configuration being created:
+    #
+    #   config = OpenSSL::Config.new
+    #     #=> #<OpenSSL::Config sections=[]>
+    #   config['default'] = {"foo"=>"bar","baz"=>"buz"}
+    #     #=> {"foo"=>"bar", "baz"=>"buz"}
+    #   puts config.to_s
+    #     #=> [ default ]
+    #     #   foo=bar
+    #     #   baz=buz
+    #
+    # It's important to note that this will essentially merge any of the keys
+    # in +pairs+ with the existing +section+. For example:
+    #
+    #   config['default']
+    #     #=> {"foo"=>"bar", "baz"=>"buz"}
+    #   config['default'] = {"foo" => "changed"}
+    #     #=> {"foo"=>"changed"}
+    #   config['default']
+    #     #=> {"foo"=>"changed", "baz"=>"buz"}
+    #
+    def []=(section, pairs)
+      check_modify
+      @data[section] ||= {}
+      pairs.each do |key, value|
+        self.add_value(section, key, value)
+      end
+    end
+
+    ##
+    # Get the names of all sections in the current configuration
+    def sections
+      @data.keys
+    end
+
+    ##
+    # Get the parsable form of the current configuration
+    #
+    # Given the following configuration being created:
+    #
+    #   config = OpenSSL::Config.new
+    #     #=> #<OpenSSL::Config sections=[]>
+    #   config['default'] = {"foo"=>"bar","baz"=>"buz"}
+    #     #=> {"foo"=>"bar", "baz"=>"buz"}
+    #   puts config.to_s
+    #     #=> [ default ]
+    #     #   foo=bar
+    #     #   baz=buz
+    #
+    # You can parse get the serialized configuration using #to_s and then parse
+    # it later:
+    #
+    #   serialized_config = config.to_s
+    #   # much later...
+    #   new_config = OpenSSL::Config.parse(serialized_config)
+    #     #=> #<OpenSSL::Config sections=["default"]>
+    #   puts new_config
+    #     #=> [ default ]
+    #         foo=bar
+    #         baz=buz
+    #
+    def to_s
+      ary = []
+      @data.keys.sort.each do |section|
+        ary << "[ #{section} ]\n"
+        @data[section].keys.each do |key|
+          ary << "#{key}=#{@data[section][key]}\n"
+        end
+        ary << "\n"
+      end
+      ary.join
+    end
+
+    ##
+    # For a block.
+    #
+    # Receive the section and its pairs for the current configuration.
+    #
+    #   config.each do |section, key, value|
+    #     # ...
+    #   end
+    #
+    def each
+      @data.each do |section, hash|
+        hash.each do |key, value|
+          yield [section, key, value]
+        end
+      end
+    end
+
+    ##
+    # String representation of this configuration object, including the class
+    # name and its sections.
+    def inspect
+      "#<#{self.class.name} sections=#{sections.inspect}>"
+    end
+
+  protected
+
+    def data # :nodoc:
+      @data
+    end
+
+  private
+
+    def initialize_copy(other)
+      @data = other.data.dup
+    end
+
+    def check_modify
+      raise TypeError.new("Insecure: can't modify OpenSSL config") if frozen?
+    end
+
+    def get_key_string(section, key)
+      Config.get_key_string(@data, section, key)
+    end
+  end
+end
diff --git a/lib/openssl/digest.rb b/lib/openssl/digest.rb
new file mode 100644
index 0000000..97ccbc9
--- /dev/null
+++ b/lib/openssl/digest.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: false
+#--
+# = Ruby-space predefined Digest subclasses
+#
+# = Info
+# 'OpenSSL for Ruby 2' project
+# Copyright (C) 2002  Michal Rokos <m.rokos at sh.cvut.cz>
+# All rights reserved.
+#
+# = Licence
+# This program is licensed under the same licence as Ruby.
+# (See the file 'LICENCE'.)
+#++
+
+module OpenSSL
+  class Digest
+
+    alg = %w(MD2 MD4 MD5 MDC2 RIPEMD160 SHA1)
+    if OPENSSL_VERSION_NUMBER < 0x10100000
+      alg += %w(DSS DSS1 SHA)
+    end
+    if OPENSSL_VERSION_NUMBER > 0x00908000
+      alg += %w(SHA224 SHA256 SHA384 SHA512)
+    end
+
+    # Return the +data+ hash computed with +name+ Digest. +name+ is either the
+    # long name or short name of a supported digest algorithm.
+    #
+    # === Examples
+    #
+    #   OpenSSL::Digest.digest("SHA256", "abc")
+    #
+    # which is equivalent to:
+    #
+    #   OpenSSL::Digest::SHA256.digest("abc")
+
+    def self.digest(name, data)
+      super(data, name)
+    end
+
+    alg.each{|name|
+      klass = Class.new(self) {
+        define_method(:initialize, ->(data = nil) {super(name, data)})
+      }
+      singleton = (class << klass; self; end)
+      singleton.class_eval{
+        define_method(:digest){|data| new.digest(data) }
+        define_method(:hexdigest){|data| new.hexdigest(data) }
+      }
+      const_set(name, klass)
+    }
+
+    # Deprecated.
+    #
+    # This class is only provided for backwards compatibility.
+    # Use OpenSSL::Digest instead.
+    class Digest < Digest; end # :nodoc:
+    deprecate_constant :Digest
+
+  end # Digest
+
+  # Returns a Digest subclass by +name+.
+  #
+  #   require 'openssl'
+  #
+  #   OpenSSL::Digest("MD5")
+  #   # => OpenSSL::Digest::MD5
+  #
+  #   Digest("Foo")
+  #   # => NameError: wrong constant name Foo
+
+  def Digest(name)
+    OpenSSL::Digest.const_get(name)
+  end
+
+  module_function :Digest
+
+end # OpenSSL
diff --git a/lib/openssl/pkey.rb b/lib/openssl/pkey.rb
new file mode 100644
index 0000000..9af5f78
--- /dev/null
+++ b/lib/openssl/pkey.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: false
+module OpenSSL
+  module PKey
+    if defined?(OpenSSL::PKey::DH)
+
+    class DH
+      # :nodoc:
+      DEFAULT_1024 = new <<-_end_of_pem_
+-----BEGIN DH PARAMETERS-----
+MIGHAoGBAJ0lOVy0VIr/JebWn0zDwY2h+rqITFOpdNr6ugsgvkDXuucdcChhYExJ
+AV/ZD2AWPbrTqV76mGRgJg4EddgT1zG0jq3rnFdMj2XzkBYx3BVvfR0Arnby0RHR
+T4h7KZ/2zmjvV+eF8kBUHBJAojUlzxKj4QeO2x20FP9X5xmNUXeDAgEC
+-----END DH PARAMETERS-----
+      _end_of_pem_
+
+      # :nodoc:
+      DEFAULT_2048 = new <<-_end_of_pem_
+-----BEGIN DH PARAMETERS-----
+MIIBCAKCAQEA7E6kBrYiyvmKAMzQ7i8WvwVk9Y/+f8S7sCTN712KkK3cqd1jhJDY
+JbrYeNV3kUIKhPxWHhObHKpD1R84UpL+s2b55+iMd6GmL7OYmNIT/FccKhTcveab
+VBmZT86BZKYyf45hUF9FOuUM9xPzuK3Vd8oJQvfYMCd7LPC0taAEljQLR4Edf8E6
+YoaOffgTf5qxiwkjnlVZQc3whgnEt9FpVMvQ9eknyeGB5KHfayAc3+hUAvI3/Cr3
+1bNveX5wInh5GDx1FGhKBZ+s1H+aedudCm7sCgRwv8lKWYGiHzObSma8A86KG+MD
+7Lo5JquQ3DlBodj3IDyPrxIv96lvRPFtAwIBAg==
+-----END DH PARAMETERS-----
+      _end_of_pem_
+    end
+
+    # :nodoc:
+    DEFAULT_TMP_DH_CALLBACK = lambda { |ctx, is_export, keylen|
+      warn "using default DH parameters." if $VERBOSE
+      case keylen
+      when 1024 then OpenSSL::PKey::DH::DEFAULT_1024
+      when 2048 then OpenSSL::PKey::DH::DEFAULT_2048
+      else
+        nil
+      end
+    }
+
+    else
+      DEFAULT_TMP_DH_CALLBACK = nil
+    end
+  end
+end
diff --git a/lib/openssl/ssl.rb b/lib/openssl/ssl.rb
new file mode 100644
index 0000000..f40a451
--- /dev/null
+++ b/lib/openssl/ssl.rb
@@ -0,0 +1,400 @@
+# frozen_string_literal: false
+=begin
+= Info
+  'OpenSSL for Ruby 2' project
+  Copyright (C) 2001 GOTOU YUUZOU <gotoyuzo at notwork.org>
+  All rights reserved.
+
+= Licence
+  This program is licensed under the same licence as Ruby.
+  (See the file 'LICENCE'.)
+=end
+
+require "openssl/buffering"
+require "io/nonblock"
+
+module OpenSSL
+  module SSL
+    class SSLContext
+      DEFAULT_PARAMS = { # :nodoc:
+        :ssl_version => "SSLv23",
+        :verify_mode => OpenSSL::SSL::VERIFY_PEER,
+        :verify_hostname => true,
+        :options => -> {
+          opts = OpenSSL::SSL::OP_ALL
+          opts &= ~OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS
+          opts |= OpenSSL::SSL::OP_NO_COMPRESSION if defined?(OpenSSL::SSL::OP_NO_COMPRESSION)
+          opts |= OpenSSL::SSL::OP_NO_SSLv2 | OpenSSL::SSL::OP_NO_SSLv3
+          opts
+        }.call
+      }
+
+      if !(OpenSSL::OPENSSL_VERSION.start_with?("OpenSSL") &&
+           OpenSSL::OPENSSL_VERSION_NUMBER >= 0x10100000)
+        DEFAULT_PARAMS.merge!(
+          ciphers: %w{
+            ECDHE-ECDSA-AES128-GCM-SHA256
+            ECDHE-RSA-AES128-GCM-SHA256
+            ECDHE-ECDSA-AES256-GCM-SHA384
+            ECDHE-RSA-AES256-GCM-SHA384
+            DHE-RSA-AES128-GCM-SHA256
+            DHE-DSS-AES128-GCM-SHA256
+            DHE-RSA-AES256-GCM-SHA384
+            DHE-DSS-AES256-GCM-SHA384
+            ECDHE-ECDSA-AES128-SHA256
+            ECDHE-RSA-AES128-SHA256
+            ECDHE-ECDSA-AES128-SHA
+            ECDHE-RSA-AES128-SHA
+            ECDHE-ECDSA-AES256-SHA384
+            ECDHE-RSA-AES256-SHA384
+            ECDHE-ECDSA-AES256-SHA
+            ECDHE-RSA-AES256-SHA
+            DHE-RSA-AES128-SHA256
+            DHE-RSA-AES256-SHA256
+            DHE-RSA-AES128-SHA
+            DHE-RSA-AES256-SHA
+            DHE-DSS-AES128-SHA256
+            DHE-DSS-AES256-SHA256
+            DHE-DSS-AES128-SHA
+            DHE-DSS-AES256-SHA
+            AES128-GCM-SHA256
+            AES256-GCM-SHA384
+            AES128-SHA256
+            AES256-SHA256
+            AES128-SHA
+            AES256-SHA
+          }.join(":"),
+        )
+      end
+
+      DEFAULT_CERT_STORE = OpenSSL::X509::Store.new # :nodoc:
+      DEFAULT_CERT_STORE.set_default_paths
+      DEFAULT_CERT_STORE.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL
+
+      # A callback invoked when DH parameters are required.
+      #
+      # The callback is invoked with the Session for the key exchange, an
+      # flag indicating the use of an export cipher and the keylength
+      # required.
+      #
+      # The callback must return an OpenSSL::PKey::DH instance of the correct
+      # key length.
+
+      attr_accessor :tmp_dh_callback
+
+      # A callback invoked at connect time to distinguish between multiple
+      # server names.
+      #
+      # The callback is invoked with an SSLSocket and a server name.  The
+      # callback must return an SSLContext for the server name or nil.
+      attr_accessor :servername_cb if ExtConfig::HAVE_TLSEXT_HOST_NAME
+
+      # call-seq:
+      #    SSLContext.new => ctx
+      #    SSLContext.new(:TLSv1) => ctx
+      #    SSLContext.new("SSLv23_client") => ctx
+      #
+      # You can get a list of valid methods with OpenSSL::SSL::SSLContext::METHODS
+      def initialize(version = nil)
+        self.options |= OpenSSL::SSL::OP_ALL
+        self.ssl_version = version if version
+      end
+
+      ##
+      # call-seq:
+      #   ctx.set_params(params = {}) -> params
+      #
+      # Sets saner defaults optimized for the use with HTTP-like protocols.
+      #
+      # If a Hash +params+ is given, the parameters are overridden with it.
+      # The keys in +params+ must be assignment methods on SSLContext.
+      #
+      # If the verify_mode is not VERIFY_NONE and ca_file, ca_path and
+      # cert_store are not set then the system default certificate store is
+      # used.
+      def set_params(params={})
+        params = DEFAULT_PARAMS.merge(params)
+        params.each{|name, value| self.__send__("#{name}=", value) }
+        if self.verify_mode != OpenSSL::SSL::VERIFY_NONE
+          unless self.ca_file or self.ca_path or self.cert_store
+            self.cert_store = DEFAULT_CERT_STORE
+          end
+        end
+        return params
+      end
+    end
+
+    module SocketForwarder
+      def addr
+        to_io.addr
+      end
+
+      def peeraddr
+        to_io.peeraddr
+      end
+
+      def setsockopt(level, optname, optval)
+        to_io.setsockopt(level, optname, optval)
+      end
+
+      def getsockopt(level, optname)
+        to_io.getsockopt(level, optname)
+      end
+
+      def fcntl(*args)
+        to_io.fcntl(*args)
+      end
+
+      def closed?
+        to_io.closed?
+      end
+
+      def do_not_reverse_lookup=(flag)
+        to_io.do_not_reverse_lookup = flag
+      end
+    end
+
+    def verify_certificate_identity(cert, hostname)
+      should_verify_common_name = true
+      cert.extensions.each{|ext|
+        next if ext.oid != "subjectAltName"
+        ostr = OpenSSL::ASN1.decode(ext.to_der).value.last
+        sequence = OpenSSL::ASN1.decode(ostr.value)
+        sequence.value.each{|san|
+          case san.tag
+          when 2 # dNSName in GeneralName (RFC5280)
+            should_verify_common_name = false
+            return true if verify_hostname(hostname, san.value)
+          when 7 # iPAddress in GeneralName (RFC5280)
+            should_verify_common_name = false
+            # follows GENERAL_NAME_print() in x509v3/v3_alt.c
+            if san.value.size == 4
+              return true if san.value.unpack('C*').join('.') == hostname
+            elsif san.value.size == 16
+              return true if san.value.unpack('n*').map { |e| sprintf("%X", e) }.join(':') == hostname
+            end
+          end
+        }
+      }
+      if should_verify_common_name
+        cert.subject.to_a.each{|oid, value|
+          if oid == "CN"
+            return true if verify_hostname(hostname, value)
+          end
+        }
+      end
+      return false
+    end
+    module_function :verify_certificate_identity
+
+    def verify_hostname(hostname, san) # :nodoc:
+      # RFC 5280, IA5String is limited to the set of ASCII characters
+      return false unless san.ascii_only?
+      return false unless hostname.ascii_only?
+
+      # See RFC 6125, section 6.4.1
+      # Matching is case-insensitive.
+      san_parts = san.downcase.split(".")
+
+      # TODO: this behavior should probably be more strict
+      return san == hostname if san_parts.size < 2
+
+      # Matching is case-insensitive.
+      host_parts = hostname.downcase.split(".")
+
+      # RFC 6125, section 6.4.3, subitem 2.
+      # If the wildcard character is the only character of the left-most
+      # label in the presented identifier, the client SHOULD NOT compare
+      # against anything but the left-most label of the reference
+      # identifier (e.g., *.example.com would match foo.example.com but
+      # not bar.foo.example.com or example.com).
+      return false unless san_parts.size == host_parts.size
+
+      # RFC 6125, section 6.4.3, subitem 1.
+      # The client SHOULD NOT attempt to match a presented identifier in
+      # which the wildcard character comprises a label other than the
+      # left-most label (e.g., do not match bar.*.example.net).
+      return false unless verify_wildcard(host_parts.shift, san_parts.shift)
+
+      san_parts.join(".") == host_parts.join(".")
+    end
+    module_function :verify_hostname
+
+    def verify_wildcard(domain_component, san_component) # :nodoc:
+      parts = san_component.split("*", -1)
+
+      return false if parts.size > 2
+      return san_component == domain_component if parts.size == 1
+
+      # RFC 6125, section 6.4.3, subitem 3.
+      # The client SHOULD NOT attempt to match a presented identifier
+      # where the wildcard character is embedded within an A-label or
+      # U-label of an internationalized domain name.
+      return false if domain_component.start_with?("xn--") && san_component != "*"
+
+      parts[0].length + parts[1].length < domain_component.length &&
+      domain_component.start_with?(parts[0]) &&
+      domain_component.end_with?(parts[1])
+    end
+    module_function :verify_wildcard
+
+    class SSLSocket
+      include Buffering
+      include SocketForwarder
+
+      if ExtConfig::HAVE_TLSEXT_HOST_NAME
+        attr_reader :hostname
+      end
+
+      # The underlying IO object.
+      attr_reader :io
+      alias :to_io :io
+
+      # The SSLContext object used in this connection.
+      attr_reader :context
+
+      # Whether to close the underlying socket as well, when the SSL/TLS
+      # connection is shut down. This defaults to +false+.
+      attr_accessor :sync_close
+
+      # call-seq:
+      #    ssl.sysclose => nil
+      #
+      # Sends "close notify" to the peer and tries to shut down the SSL
+      # connection gracefully.
+      #
+      # If sync_close is set to +true+, the underlying IO is also closed.
+      def sysclose
+        return if closed?
+        stop
+        io.close if sync_close
+      end
+
+      # call-seq:
+      #   ssl.post_connection_check(hostname) -> true
+      #
+      # Perform hostname verification following RFC 6125.
+      #
+      # This method MUST be called after calling #connect to ensure that the
+      # hostname of a remote peer has been verified.
+      def post_connection_check(hostname)
+        if peer_cert.nil?
+          msg = "Peer verification enabled, but no certificate received."
+          if using_anon_cipher?
+            msg += " Anonymous cipher suite #{cipher[0]} was negotiated. " \
+                   "Anonymous suites must be disabled to use peer verification."
+          end
+          raise SSLError, msg
+        end
+
+        unless OpenSSL::SSL.verify_certificate_identity(peer_cert, hostname)
+          raise SSLError, "hostname \"#{hostname}\" does not match the server certificate"
+        end
+        return true
+      end
+
+      # call-seq:
+      #   ssl.session -> aSession
+      #
+      # Returns the SSLSession object currently used, or nil if the session is
+      # not established.
+      def session
+        SSL::Session.new(self)
+      rescue SSL::Session::SessionError
+        nil
+      end
+
+      private
+
+      def using_anon_cipher?
+        ctx = OpenSSL::SSL::SSLContext.new
+        ctx.ciphers = "aNULL"
+        ctx.ciphers.include?(cipher)
+      end
+
+      def client_cert_cb
+        @context.client_cert_cb
+      end
+
+      def tmp_dh_callback
+        @context.tmp_dh_callback || OpenSSL::PKey::DEFAULT_TMP_DH_CALLBACK
+      end
+
+      def tmp_ecdh_callback
+        @context.tmp_ecdh_callback
+      end
+
+      def session_new_cb
+        @context.session_new_cb
+      end
+
+      def session_get_cb
+        @context.session_get_cb
+      end
+    end
+
+    ##
+    # SSLServer represents a TCP/IP server socket with Secure Sockets Layer.
+    class SSLServer
+      include SocketForwarder
+      # When true then #accept works exactly the same as TCPServer#accept
+      attr_accessor :start_immediately
+
+      # Creates a new instance of SSLServer.
+      # * +srv+ is an instance of TCPServer.
+      # * +ctx+ is an instance of OpenSSL::SSL::SSLContext.
+      def initialize(svr, ctx)
+        @svr = svr
+        @ctx = ctx
+        unless ctx.session_id_context
+          # see #6137 - session id may not exceed 32 bytes
+          prng = ::Random.new($0.hash)
+          session_id = prng.bytes(16).unpack('H*')[0]
+          @ctx.session_id_context = session_id
+        end
+        @start_immediately = true
+      end
+
+      # Returns the TCPServer passed to the SSLServer when initialized.
+      def to_io
+        @svr
+      end
+
+      # See TCPServer#listen for details.
+      def listen(backlog=5)
+        @svr.listen(backlog)
+      end
+
+      # See BasicSocket#shutdown for details.
+      def shutdown(how=Socket::SHUT_RDWR)
+        @svr.shutdown(how)
+      end
+
+      # Works similar to TCPServer#accept.
+      def accept
+        # Socket#accept returns [socket, addrinfo].
+        # TCPServer#accept returns a socket.
+        # The following comma strips addrinfo.
+        sock, = @svr.accept
+        begin
+          ssl = OpenSSL::SSL::SSLSocket.new(sock, @ctx)
+          ssl.sync_close = true
+          ssl.accept if @start_immediately
+          ssl
+        rescue Exception => ex
+          if ssl
+            ssl.close
+          else
+            sock.close
+          end
+          raise ex
+        end
+      end
+
+      # See IO#close for details.
+      def close
+        @svr.close
+      end
+    end
+  end
+end
diff --git a/lib/openssl/x509.rb b/lib/openssl/x509.rb
new file mode 100644
index 0000000..aef3456
--- /dev/null
+++ b/lib/openssl/x509.rb
@@ -0,0 +1,176 @@
+# frozen_string_literal: false
+#--
+# = Ruby-space definitions that completes C-space funcs for X509 and subclasses
+#
+# = Info
+# 'OpenSSL for Ruby 2' project
+# Copyright (C) 2002  Michal Rokos <m.rokos at sh.cvut.cz>
+# All rights reserved.
+#
+# = Licence
+# This program is licensed under the same licence as Ruby.
+# (See the file 'LICENCE'.)
+#++
+
+module OpenSSL
+  module X509
+    class ExtensionFactory
+      def create_extension(*arg)
+        if arg.size > 1
+          create_ext(*arg)
+        else
+          send("create_ext_from_"+arg[0].class.name.downcase, arg[0])
+        end
+      end
+
+      def create_ext_from_array(ary)
+        raise ExtensionError, "unexpected array form" if ary.size > 3
+        create_ext(ary[0], ary[1], ary[2])
+      end
+
+      def create_ext_from_string(str) # "oid = critical, value"
+        oid, value = str.split(/=/, 2)
+        oid.strip!
+        value.strip!
+        create_ext(oid, value)
+      end
+
+      def create_ext_from_hash(hash)
+        create_ext(hash["oid"], hash["value"], hash["critical"])
+      end
+    end
+
+    class Extension
+      def to_s # "oid = critical, value"
+        str = self.oid
+        str << " = "
+        str << "critical, " if self.critical?
+        str << self.value.gsub(/\n/, ", ")
+      end
+
+      def to_h # {"oid"=>sn|ln, "value"=>value, "critical"=>true|false}
+        {"oid"=>self.oid,"value"=>self.value,"critical"=>self.critical?}
+      end
+
+      def to_a
+        [ self.oid, self.value, self.critical? ]
+      end
+    end
+
+    class Name
+      module RFC2253DN
+        Special = ',=+<>#;'
+        HexChar = /[0-9a-fA-F]/
+        HexPair = /#{HexChar}#{HexChar}/
+        HexString = /#{HexPair}+/
+        Pair = /\\(?:[#{Special}]|\\|"|#{HexPair})/
+        StringChar = /[^\\"#{Special}]/
+        QuoteChar = /[^\\"]/
+        AttributeType = /[a-zA-Z][0-9a-zA-Z]*|[0-9]+(?:\.[0-9]+)*/
+        AttributeValue = /
+          (?!["#])((?:#{StringChar}|#{Pair})*)|
+          \#(#{HexString})|
+          "((?:#{QuoteChar}|#{Pair})*)"
+        /x
+        TypeAndValue = /\A(#{AttributeType})=#{AttributeValue}/
+
+        module_function
+
+        def expand_pair(str)
+          return nil unless str
+          return str.gsub(Pair){
+            pair = $&
+            case pair.size
+            when 2 then pair[1,1]
+            when 3 then Integer("0x#{pair[1,2]}").chr
+            else raise OpenSSL::X509::NameError, "invalid pair: #{str}"
+            end
+          }
+        end
+
+        def expand_hexstring(str)
+          return nil unless str
+          der = str.gsub(HexPair){$&.to_i(16).chr }
+          a1 = OpenSSL::ASN1.decode(der)
+          return a1.value, a1.tag
+        end
+
+        def expand_value(str1, str2, str3)
+          value = expand_pair(str1)
+          value, tag = expand_hexstring(str2) unless value
+          value = expand_pair(str3) unless value
+          return value, tag
+        end
+
+        def scan(dn)
+          str = dn
+          ary = []
+          while true
+            if md = TypeAndValue.match(str)
+              remain = md.post_match
+              type = md[1]
+              value, tag = expand_value(md[2], md[3], md[4]) rescue nil
+              if value
+                type_and_value = [type, value]
+                type_and_value.push(tag) if tag
+                ary.unshift(type_and_value)
+                if remain.length > 2 && remain[0] == ?,
+                  str = remain[1..-1]
+                  next
+                elsif remain.length > 2 && remain[0] == ?+
+                  raise OpenSSL::X509::NameError,
+                    "multi-valued RDN is not supported: #{dn}"
+                elsif remain.empty?
+                  break
+                end
+              end
+            end
+            msg_dn = dn[0, dn.length - str.length] + " =>" + str
+            raise OpenSSL::X509::NameError, "malformed RDN: #{msg_dn}"
+          end
+          return ary
+        end
+      end
+
+      class << self
+        def parse_rfc2253(str, template=OBJECT_TYPE_TEMPLATE)
+          ary = OpenSSL::X509::Name::RFC2253DN.scan(str)
+          self.new(ary, template)
+        end
+
+        def parse_openssl(str, template=OBJECT_TYPE_TEMPLATE)
+          ary = str.scan(/\s*([^\/,]+)\s*/).collect{|i| i[0].split("=", 2) }
+          self.new(ary, template)
+        end
+
+        alias parse parse_openssl
+      end
+
+      def pretty_print(q)
+        q.object_group(self) {
+          q.text ' '
+          q.text to_s(OpenSSL::X509::Name::RFC2253)
+        }
+      end
+    end
+
+    class StoreContext
+      def cleanup
+        warn "(#{caller.first}) OpenSSL::X509::StoreContext#cleanup is deprecated with no replacement" if $VERBOSE
+      end
+    end
+
+    class Certificate
+      def pretty_print(q)
+        q.object_group(self) {
+          q.breakable
+          q.text 'subject='; q.pp self.subject; q.text ','; q.breakable
+          q.text 'issuer='; q.pp self.issuer; q.text ','; q.breakable
+          q.text 'serial='; q.pp self.serial; q.text ','; q.breakable
+          q.text 'not_before='; q.pp self.not_before; q.text ','; q.breakable
+          q.text 'not_after='; q.pp self.not_after
+        }
+      end
+    end
+  end
+end
diff --git a/openssl.gemspec b/openssl.gemspec
new file mode 100644
index 0000000..e0080f2
--- /dev/null
+++ b/openssl.gemspec
@@ -0,0 +1,24 @@
+Gem::Specification.new do |spec|
+  spec.name          = "openssl"
+  spec.version       = "2.0.4"
+  spec.authors       = ["Martin Bosslet", "SHIBATA Hiroshi", "Zachary Scott", "Kazuki Yamaguchi"]
+  spec.email         = ["ruby-core at ruby-lang.org"]
+  spec.summary       = %q{OpenSSL provides SSL, TLS and general purpose cryptography.}
+  spec.description   = %q{It wraps the OpenSSL library.}
+  spec.homepage      = "https://www.ruby-lang.org/"
+  spec.license       = "Ruby"
+
+  spec.files         = Dir["lib/**/*.rb", "ext/**/*.{c,h,rb}", "*.md", "BSDL", "LICENSE.txt"]
+  spec.require_paths = ["lib"]
+  spec.extensions    = ["ext/openssl/extconf.rb"]
+
+  spec.extra_rdoc_files = Dir["*.md"]
+  spec.rdoc_options = ["--main", "README.md"]
+
+  spec.required_ruby_version = ">= 2.3.0"
+
+  spec.add_development_dependency "rake"
+  spec.add_development_dependency "rake-compiler"
+  spec.add_development_dependency "test-unit", "~> 3.0"
+  spec.add_development_dependency "rdoc"
+end
diff --git a/sample/c_rehash.rb b/sample/c_rehash.rb
new file mode 100644
index 0000000..cd6c9d5
--- /dev/null
+++ b/sample/c_rehash.rb
@@ -0,0 +1,174 @@
+#!/usr/bin/env ruby
+
+require 'openssl'
+require 'digest/md5'
+
+class CHashDir
+  include Enumerable
+
+  def initialize(dirpath)
+    @dirpath = dirpath
+    @fingerprint_cache = @cert_cache = @crl_cache = nil
+  end
+
+  def hash_dir(silent = false)
+    # ToDo: Should lock the directory...
+    @silent = silent
+    @fingerprint_cache = Hash.new
+    @cert_cache = Hash.new
+    @crl_cache = Hash.new
+    do_hash_dir
+  end
+
+  def get_certs(name = nil)
+    if name
+      @cert_cache[hash_name(name)]
+    else
+      @cert_cache.values.flatten
+    end
+  end
+
+  def get_crls(name = nil)
+    if name
+      @crl_cache[hash_name(name)]
+    else
+      @crl_cache.values.flatten
+    end
+  end
+
+  def delete_crl(crl)
+    File.unlink(crl_filename(crl))
+    hash_dir(true)
+  end
+
+  def add_crl(crl)
+    File.open(crl_filename(crl), "w") do |f|
+      f << crl.to_pem
+    end
+    hash_dir(true)
+  end
+
+  def load_pem_file(filepath)
+    str = File.read(filepath)
+    begin
+      OpenSSL::X509::Certificate.new(str)
+    rescue
+      begin
+        OpenSSL::X509::CRL.new(str)
+      rescue
+        begin
+          OpenSSL::X509::Request.new(str)
+        rescue
+          nil
+        end
+      end
+    end
+  end
+
+private
+
+  def crl_filename(crl)
+    path(hash_name(crl.issuer)) + '.pem'
+  end
+
+  def do_hash_dir
+    Dir.chdir(@dirpath) do
+      delete_symlink
+      Dir.glob('*.pem') do |pemfile|
+        cert = load_pem_file(pemfile)
+        case cert
+        when OpenSSL::X509::Certificate
+          link_hash_cert(pemfile, cert)
+        when OpenSSL::X509::CRL
+          link_hash_crl(pemfile, cert)
+        else
+          STDERR.puts("WARNING: #{pemfile} does not contain a certificate or CRL: skipping") unless @silent
+        end
+      end
+    end
+  end
+
+  def delete_symlink
+    Dir.entries(".").each do |entry|
+      next unless /^[\da-f]+\.r{0,1}\d+$/ =~ entry
+      File.unlink(entry) if FileTest.symlink?(entry)
+    end
+  end
+
+  def link_hash_cert(org_filename, cert)
+    name_hash = hash_name(cert.subject)
+    fingerprint = fingerprint(cert.to_der)
+    filepath = link_hash(org_filename, name_hash, fingerprint) { |idx|
+      "#{name_hash}.#{idx}"
+    }
+    unless filepath
+      unless @silent
+        STDERR.puts("WARNING: Skipping duplicate certificate #{org_filename}")
+      end
+    else
+      (@cert_cache[name_hash] ||= []) << path(filepath)
+    end
+  end
+
+  def link_hash_crl(org_filename, crl)
+    name_hash = hash_name(crl.issuer)
+    fingerprint = fingerprint(crl.to_der)
+    filepath = link_hash(org_filename, name_hash, fingerprint) { |idx|
+      "#{name_hash}.r#{idx}"
+    }
+    unless filepath
+      unless @silent
+        STDERR.puts("WARNING: Skipping duplicate CRL #{org_filename}")
+      end
+    else
+      (@crl_cache[name_hash] ||= []) << path(filepath)
+    end
+  end
+
+  def link_hash(org_filename, name, fingerprint)
+    idx = 0
+    filepath = nil
+    while true
+      filepath = yield(idx)
+      break unless FileTest.symlink?(filepath) or FileTest.exist?(filepath)
+      if @fingerprint_cache[filepath] == fingerprint
+        return false
+      end
+      idx += 1
+    end
+    STDOUT.puts("#{org_filename} => #{filepath}") unless @silent
+    symlink(org_filename, filepath)
+    @fingerprint_cache[filepath] = fingerprint
+    filepath
+  end
+
+  def symlink(from, to)
+    begin
+      File.symlink(from, to)
+    rescue
+      File.open(to, "w") do |f|
+        f << File.read(from)
+      end
+    end
+  end
+
+  def path(filename)
+    File.join(@dirpath, filename)
+  end
+
+  def hash_name(name)
+    sprintf("%x", name.hash)
+  end
+
+  def fingerprint(der)
+    Digest::MD5.hexdigest(der).upcase
+  end
+end
+
+if $0 == __FILE__
+  dirlist = ARGV
+  dirlist << '/usr/ssl/certs' if dirlist.empty?
+  dirlist.each do |dir|
+    CHashDir.new(dir).hash_dir
+  end
+end
diff --git a/sample/cert2text.rb b/sample/cert2text.rb
new file mode 100644
index 0000000..50da224
--- /dev/null
+++ b/sample/cert2text.rb
@@ -0,0 +1,23 @@
+#!/usr/bin/env ruby
+
+require 'openssl'
+include OpenSSL::X509
+
+def cert2text(cert_str)
+  [Certificate, CRL, Request].each do |klass|
+    begin
+      puts klass.new(cert_str).to_text
+      return
+    rescue
+    end
+  end
+  raise ArgumentError.new('Unknown format.')
+end
+
+if ARGV.empty?
+  cert2text(STDIN.read)
+else
+  ARGV.each do |file|
+    cert2text(File.read(file))
+  end
+end
diff --git a/sample/certstore.rb b/sample/certstore.rb
new file mode 100644
index 0000000..c6e8f81
--- /dev/null
+++ b/sample/certstore.rb
@@ -0,0 +1,161 @@
+require 'c_rehash'
+require 'crlstore'
+
+
+class CertStore
+  include OpenSSL
+  include X509
+
+  attr_reader :self_signed_ca
+  attr_reader :other_ca
+  attr_reader :ee
+  attr_reader :crl
+  attr_reader :request
+
+  def initialize(certs_dir)
+    @certs_dir = certs_dir
+    @c_store = CHashDir.new(@certs_dir)
+    @c_store.hash_dir(true)
+    @crl_store = CrlStore.new(@c_store)
+    @x509store = Store.new
+    @self_signed_ca = @other_ca = @ee = @crl = nil
+
+    # Uncomment this line to let OpenSSL to check CRL for each certs.
+    # @x509store.flags = V_FLAG_CRL_CHECK | V_FLAG_CRL_CHECK_ALL
+
+    add_path
+    scan_certs
+  end
+
+  def generate_cert(filename)
+    @c_store.load_pem_file(filename)
+  end
+
+  def verify(cert)
+    error, crl_map = do_verify(cert)
+    if error
+      [[false, cert, crl_map[cert.subject], error]]
+    else
+      @x509store.chain.collect { |c| [true, c, crl_map[c.subject], nil] }
+    end
+  end
+
+  def match_cert(cert1, cert2)
+    (cert1.issuer.cmp(cert2.issuer) == 0) and cert1.serial == cert2.serial
+  end
+
+  def is_ca?(cert)
+    case guess_cert_type(cert)
+    when CERT_TYPE_SELF_SIGNED
+      true
+    when CERT_TYPE_OTHER
+      true
+    else
+      false
+    end
+  end
+
+  def scan_certs
+    @self_signed_ca = []
+    @other_ca = []
+    @ee = []
+    @crl = []
+    @request = []
+    load_certs
+  end
+
+private
+
+  def add_path
+    @x509store.add_path(@certs_dir)
+  end
+
+  def do_verify(cert)
+    error_map = {}
+    crl_map = {}
+    result = @x509store.verify(cert) do |ok, ctx|
+      cert = ctx.current_cert
+      if ctx.current_crl
+        crl_map[cert.subject] = true
+      end
+      if ok
+        if !ctx.current_crl
+          if crl = @crl_store.find_crl(cert)
+            crl_map[cert.subject] = true
+            if crl.revoked.find { |revoked| revoked.serial == cert.serial }
+              ok = false
+              error_string = 'certification revoked'
+            end
+          end
+        end
+      end
+      error_map[cert.subject] = error_string if error_string
+      ok
+    end
+    error = if result
+      nil
+    else
+      error_map[cert.subject] || @x509store.error_string
+    end
+    return error, crl_map
+  end
+
+  def load_certs
+    @c_store.get_certs.each do |certfile|
+      cert = generate_cert(certfile)
+      case guess_cert_type(cert)
+      when CERT_TYPE_SELF_SIGNED
+        @self_signed_ca << cert
+      when CERT_TYPE_OTHER
+        @other_ca << cert
+      when CERT_TYPE_EE
+        @ee << cert
+      else
+        raise "Unknown cert type."
+      end
+    end
+    @c_store.get_crls.each do |crlfile|
+      @crl << generate_cert(crlfile)
+    end
+  end
+
+  CERT_TYPE_SELF_SIGNED = 0
+  CERT_TYPE_OTHER = 1
+  CERT_TYPE_EE = 2
+  def guess_cert_type(cert)
+    ca = self_signed = is_cert_self_signed(cert)
+    cert.extensions.each do |ext|
+      # Ignores criticality of extensions.  It's 'guess'ing.
+      case ext.oid
+      when 'basicConstraints'
+        /CA:(TRUE|FALSE), pathlen:(\d+)/ =~ ext.value
+        ca = ($1 == 'TRUE') unless ca
+      when 'keyUsage'
+        usage = ext.value.split(/\s*,\s*/)
+        ca = usage.include?('Certificate Sign') unless ca
+      when 'nsCertType'
+        usage = ext.value.split(/\s*,\s*/)
+        ca = usage.include?('SSL CA') unless ca
+      end
+    end
+    if ca
+      if self_signed
+        CERT_TYPE_SELF_SIGNED
+      else
+        CERT_TYPE_OTHER
+      end
+    else
+      CERT_TYPE_EE
+    end
+  end
+
+  def is_cert_self_signed(cert)
+    # cert.subject.cmp(cert.issuer) == 0
+    cert.subject.to_s == cert.issuer.to_s
+  end
+end
+
+
+if $0 == __FILE__
+  c = CertStore.new("trust_certs")
+end
diff --git a/sample/cipher.rb b/sample/cipher.rb
new file mode 100644
index 0000000..a33dc3e
--- /dev/null
+++ b/sample/cipher.rb
@@ -0,0 +1,54 @@
+#!/usr/bin/env ruby
+require 'openssl'
+
+def crypt_by_password(alg, pass, salt, text)
+  puts "--Setup--"
+  puts %(cipher alg:    "#{alg}")
+  puts %(plain text:    "#{text}")
+  puts %(password:      "#{pass}")
+  puts %(salt:          "#{salt}")
+  puts
+
+  puts "--Encrypting--"
+  enc = OpenSSL::Cipher.new(alg)
+  enc.encrypt
+  enc.pkcs5_keyivgen(pass, salt)
+  cipher =  enc.update(text)
+  cipher << enc.final
+  puts %(encrypted text: #{cipher.inspect})
+  puts
+
+  puts "--Decrypting--"
+  dec = OpenSSL::Cipher.new(alg)
+  dec.decrypt
+  dec.pkcs5_keyivgen(pass, salt)
+  plain =  dec.update(cipher)
+  plain << dec.final
+  puts %(decrypted text: "#{plain}")
+  puts
+end
+
+def ciphers
+  ciphers = OpenSSL::Cipher.ciphers.sort
+  ciphers.each{|i|
+    if i.upcase != i && ciphers.include?(i.upcase)
+      ciphers.delete(i)
+    end
+  }
+  return ciphers
+end
+
+puts "Supported ciphers in #{OpenSSL::OPENSSL_VERSION}:"
+ciphers.each_with_index{|name, i|
+  printf("%-15s", name)
+  puts if (i + 1) % 5 == 0
+}
+puts
+puts
+
+alg  = ARGV.shift || ciphers.first
+pass = "secret password"
+salt = "8 octets"        # or nil
+text = "abcdefghijklmnopqrstuvwxyz"
+
+crypt_by_password(alg, pass, salt, text)
diff --git a/sample/crlstore.rb b/sample/crlstore.rb
new file mode 100644
index 0000000..e3a5925
--- /dev/null
+++ b/sample/crlstore.rb
@@ -0,0 +1,122 @@
+begin
+  require 'http-access2'
+rescue LoadError
+  STDERR.puts("Cannot load http-access2.  CRL might not be fetched.")
+end
+require 'c_rehash'
+
+
+class CrlStore
+  def initialize(c_store)
+    @c_store = c_store
+    @c_store.hash_dir(true)
+  end
+
+  def find_crl(cert)
+    do_find_crl(cert)
+  end
+
+private
+
+  def do_find_crl(cert)
+    unless ca = find_ca(cert)
+      return nil
+    end
+    unless crlfiles = @c_store.get_crls(ca.subject)
+      if crl = renew_crl(cert, ca)
+        @c_store.add_crl(crl)
+        return crl
+      end
+      return nil
+    end
+    crlfiles.each do |crlfile|
+      next unless crl = load_crl(crlfile)
+      if crl.next_update < Time.now
+        if new_crl = renew_crl(cert, ca)
+          @c_store.delete_crl(crl)
+          @c_store.add_crl(new_crl)
+          crl = new_crl
+        end
+      end
+      if check_valid(crl, ca)
+        return crl
+      end
+    end
+    nil
+  end
+
+  def find_ca(cert)
+    @c_store.get_certs(cert.issuer).each do |cafile|
+      ca = load_cert(cafile)
+      if cert.verify(ca.public_key)
+        return ca
+      end
+    end
+    nil
+  end
+
+  def fetch(location)
+    if /\AURI:(.*)\z/ =~ location
+      begin
+        c = HTTPAccess2::Client.new(ENV['http_proxy'] || ENV['HTTP_PROXY'])
+        c.get_content($1)
+      rescue NameError, StandardError
+        nil
+      end
+    else
+      nil
+    end
+  end
+
+  def load_cert(certfile)
+    load_cert_str(File.read(certfile))
+  end
+
+  def load_crl(crlfile)
+    load_crl_str(File.read(crlfile))
+  end
+
+  def load_cert_str(cert_str)
+    OpenSSL::X509::Certificate.new(cert_str)
+  end
+
+  def load_crl_str(crl_str)
+    OpenSSL::X509::CRL.new(crl_str)
+  end
+
+  def check_valid(crl, ca)
+    unless crl.verify(ca.public_key)
+      return false
+    end
+    crl.last_update <= Time.now
+  end
+
+  RE_CDP = /\AcrlDistributionPoints\z/
+  def get_cdp(cert)
+    if cdp_ext = cert.extensions.find { |ext| RE_CDP =~ ext.oid }
+      cdp_ext.value.chomp
+    else
+      false
+    end
+  end
+
+  def renew_crl(cert, ca)
+    if cdp = get_cdp(cert)
+      if new_crl_str = fetch(cdp)
+        new_crl = load_crl_str(new_crl_str)
+        if check_valid(new_crl, ca)
+          return new_crl
+        end
+      end
+    end
+    false
+  end
+end
+
+if $0 == __FILE__
+  dir = "trust_certs"
+  c_store = CHashDir.new(dir)
+  s = CrlStore.new(c_store)
+  c = OpenSSL::X509::Certificate.new(File.read("cert_store/google_codesign.pem"))
+  p s.find_crl(c)
+end
diff --git a/sample/echo_cli.rb b/sample/echo_cli.rb
new file mode 100644
index 0000000..069a21e
--- /dev/null
+++ b/sample/echo_cli.rb
@@ -0,0 +1,44 @@
+#!/usr/bin/env ruby
+
+require 'socket'
+require 'openssl'
+require 'optparse'
+
+options = ARGV.getopts("p:c:k:C:")
+
+host      = ARGV[0] || "localhost"
+port      = options["p"] || "2000"
+cert_file = options["c"]
+key_file  = options["k"]
+ca_path   = options["C"]
+
+ctx = OpenSSL::SSL::SSLContext.new()
+if cert_file && key_file
+  ctx.cert = OpenSSL::X509::Certificate.new(File::read(cert_file))
+  ctx.key  = OpenSSL::PKey::RSA.new(File::read(key_file))
+end
+if ca_path
+  ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
+  ctx.ca_path = ca_path
+else
+  $stderr.puts "!!! WARNING: PEER CERTIFICATE WON'T BE VERIFIED !!!"
+end
+
+s = TCPSocket.new(host, port)
+ssl = OpenSSL::SSL::SSLSocket.new(s, ctx)
+ssl.connect # start SSL session
+p ssl.peer_cert
+errors = Hash.new
+OpenSSL::X509.constants.grep(/^V_(ERR_|OK)/).each do |name|
+  errors[OpenSSL::X509.const_get(name)] = name
+end
+p errors[ssl.verify_result]
+
+ssl.sync_close = true  # if true the underlying socket will be
+                       # closed in SSLSocket#close. (default: false)
+while line = $stdin.gets
+  ssl.write line
+  puts ssl.gets.inspect
+end
+
+ssl.close
diff --git a/sample/echo_svr.rb b/sample/echo_svr.rb
new file mode 100644
index 0000000..719de6b
--- /dev/null
+++ b/sample/echo_svr.rb
@@ -0,0 +1,65 @@
+#!/usr/bin/env ruby
+
+require 'socket'
+require 'openssl'
+require 'optparse'
+
+options = ARGV.getopts("p:c:k:C:")
+
+port      = options["p"] || "2000"
+cert_file = options["c"]
+key_file  = options["k"]
+ca_path   = options["C"]
+
+if cert_file && key_file
+  cert = OpenSSL::X509::Certificate.new(File::read(cert_file))
+  key  = OpenSSL::PKey::RSA.new(File::read(key_file))
+else
+  key = OpenSSL::PKey::RSA.new(512){ print "." }
+  puts
+  cert = OpenSSL::X509::Certificate.new
+  cert.version = 2
+  cert.serial = 0
+  name = OpenSSL::X509::Name.new([["C","JP"],["O","TEST"],["CN","localhost"]])
+  cert.subject = name
+  cert.issuer = name
+  cert.not_before = Time.now
+  cert.not_after = Time.now + 3600
+  cert.public_key = key.public_key
+  ef = OpenSSL::X509::ExtensionFactory.new(nil,cert)
+  cert.extensions = [
+    ef.create_extension("basicConstraints","CA:FALSE"),
+    ef.create_extension("subjectKeyIdentifier","hash"),
+    ef.create_extension("extendedKeyUsage","serverAuth"),
+    ef.create_extension("keyUsage",
+                        "keyEncipherment,dataEncipherment,digitalSignature")
+  ]
+  ef.issuer_certificate = cert
+  cert.add_extension ef.create_extension("authorityKeyIdentifier",
+                                         "keyid:always,issuer:always")
+  cert.sign(key, OpenSSL::Digest::SHA1.new)
+end
+
+ctx = OpenSSL::SSL::SSLContext.new()
+ctx.key = key
+ctx.cert = cert
+if ca_path
+  ctx.verify_mode =
+    OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
+  ctx.ca_path = ca_path
+else
+  $stderr.puts "!!! WARNING: PEER CERTIFICATE WON'T BE VERIFIED !!!"
+end
+
+tcps = TCPServer.new(port)
+ssls = OpenSSL::SSL::SSLServer.new(tcps, ctx)
+loop do
+  ns = ssls.accept
+  puts "connected from #{ns.peeraddr}"
+  while line = ns.gets
+    puts line.inspect
+    ns.write line
+  end
+  puts "connection closed"
+  ns.close
+end
diff --git a/sample/gen_csr.rb b/sample/gen_csr.rb
new file mode 100644
index 0000000..4228707
--- /dev/null
+++ b/sample/gen_csr.rb
@@ -0,0 +1,51 @@
+#!/usr/bin/env ruby
+
+require 'optparse'
+require 'openssl'
+
+include OpenSSL
+
+def usage
+  myname = File::basename($0)
+  $stderr.puts <<EOS
+Usage: #{myname} [--key keypair_file] name
+  name ... ex. /C=JP/O=RRR/OU=CA/CN=NaHi/emailAddress=nahi at example.org
+EOS
+  exit
+end
+
+options = ARGV.getopts(nil, "key:", "csrout:", "keyout:")
+keypair_file = options["key"]
+csrout = options["csrout"] || "csr.pem"
+keyout = options["keyout"] || "keypair.pem"
+
+$stdout.sync = true
+name_str = ARGV.shift or usage()
+name = X509::Name.parse(name_str)
+
+keypair = nil
+if keypair_file
+  keypair = PKey::RSA.new(File.open(keypair_file).read)
+else
+  keypair = PKey::RSA.new(1024) { putc "." }
+  puts
+  puts "Writing #{keyout}..."
+  File.open(keyout, "w", 0400) do |f|
+    f << keypair.to_pem
+  end
+end
+
+puts "Generating CSR for #{name_str}"
+
+req = X509::Request.new
+req.version = 0
+req.subject = name
+req.public_key = keypair.public_key
+req.sign(keypair, Digest::MD5.new)
+
+puts "Writing #{csrout}..."
+File.open(csrout, "w") do |f|
+  f << req.to_pem
+end
+puts req.to_text
+puts req.to_pem
diff --git a/sample/smime_read.rb b/sample/smime_read.rb
new file mode 100644
index 0000000..17394f9
--- /dev/null
+++ b/sample/smime_read.rb
@@ -0,0 +1,23 @@
+require 'optparse'
+require 'openssl'
+include OpenSSL
+
+options = ARGV.getopts("c:k:C:")
+
+cert_file = options["c"]
+key_file  = options["k"]
+ca_path   = options["C"]
+
+data = $stdin.read
+
+cert = X509::Certificate.new(File::read(cert_file))
+key = PKey::RSA.new(File::read(key_file))
+p7enc = PKCS7::read_smime(data)
+data = p7enc.decrypt(key, cert)
+
+store = X509::Store.new
+store.add_path(ca_path)
+p7sig = PKCS7::read_smime(data)
+if p7sig.verify([], store)
+  puts p7sig.data
+end
diff --git a/sample/smime_write.rb b/sample/smime_write.rb
new file mode 100644
index 0000000..5a5236c
--- /dev/null
+++ b/sample/smime_write.rb
@@ -0,0 +1,23 @@
+require 'openssl'
+require 'optparse'
+include OpenSSL
+
+options = ARGV.getopts("c:k:r:")
+
+cert_file = options["c"]
+key_file  = options["k"]
+rcpt_file = options["r"]
+
+cert = X509::Certificate.new(File::read(cert_file))
+key = PKey::RSA.new(File::read(key_file))
+
+data  = "Content-Type: text/plain\r\n"
+data << "\r\n"
+data << "This is a clear-signed message.\r\n"
+
+p7sig  = PKCS7::sign(cert, key, data, [], PKCS7::DETACHED)
+smime0 = PKCS7::write_smime(p7sig)
+
+rcpt  = X509::Certificate.new(File::read(rcpt_file))
+p7enc = PKCS7::encrypt([rcpt], smime0)
+print PKCS7::write_smime(p7enc)
diff --git a/sample/wget.rb b/sample/wget.rb
new file mode 100644
index 0000000..ee63720
--- /dev/null
+++ b/sample/wget.rb
@@ -0,0 +1,34 @@
+#!/usr/bin/env ruby
+
+require 'net/https'
+require 'optparse'
+
+options = ARGV.getopts('C:')
+
+cert_store = options["C"]
+
+uri = URI.parse(ARGV[0])
+if proxy = ENV['HTTP_PROXY']
+  prx_uri = URI.parse(proxy)
+  prx_host = prx_uri.host
+  prx_port = prx_uri.port
+end
+
+h = Net::HTTP.new(uri.host, uri.port, prx_host, prx_port)
+h.set_debug_output($stderr) if $DEBUG
+if uri.scheme == "https"
+  h.use_ssl = true
+  if cert_store
+    if File.directory?(cert_store)
+      h.ca_path = cert_store
+    else
+      h.ca_file = cert_store
+    end
+  end
+end
+
+path = uri.path.empty? ? "/" : uri.path
+h.get2(path){|resp|
+  STDERR.puts h.peer_cert.inspect if h.peer_cert
+  print resp.body
+}
diff --git a/test/envutil.rb b/test/envutil.rb
new file mode 100644
index 0000000..89332b3
--- /dev/null
+++ b/test/envutil.rb
@@ -0,0 +1,316 @@
+# -*- coding: us-ascii -*-
+require "timeout"
+require "rbconfig"
+require "pp"
+
+module EnvUtil
+  def rubybin
+    ENV["RUBY"] || RbConfig.ruby
+  end
+  module_function :rubybin
+
+  LANG_ENVS = %w"LANG LC_ALL LC_CTYPE"
+
+  def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr = false,
+                  encoding: nil, timeout: 10, reprieve: 1,
+                  stdout_filter: nil, stderr_filter: nil,
+                  rubybin: EnvUtil.rubybin,
+                  **opt)
+    in_c, in_p = IO.pipe
+    out_p, out_c = IO.pipe if capture_stdout
+    err_p, err_c = IO.pipe if capture_stderr && capture_stderr != :merge_to_stdout
+    opt[:in] = in_c
+    opt[:out] = out_c if capture_stdout
+    opt[:err] = capture_stderr == :merge_to_stdout ? out_c : err_c if capture_stderr
+    if encoding
+      out_p.set_encoding(encoding) if out_p
+      err_p.set_encoding(encoding) if err_p
+    end
+    c = "C"
+    child_env = {}
+    LANG_ENVS.each {|lc| child_env[lc] = c}
+    if Array === args and Hash === args.first
+      child_env.update(args.shift)
+    end
+    args = [args] if args.kind_of?(String)
+    pid = spawn(child_env, rubybin, *args, **opt)
+    in_c.close
+    out_c.close if capture_stdout
+    err_c.close if capture_stderr && capture_stderr != :merge_to_stdout
+    if block_given?
+      return yield in_p, out_p, err_p, pid
+    else
+      th_stdout = Thread.new { out_p.read } if capture_stdout
+      th_stderr = Thread.new { err_p.read } if capture_stderr && capture_stderr != :merge_to_stdout
+      in_p.write stdin_data.to_str unless stdin_data.empty?
+      in_p.close
+      if (!th_stdout || th_stdout.join(timeout)) && (!th_stderr || th_stderr.join(timeout))
+        stdout = th_stdout.value if capture_stdout
+        stderr = th_stderr.value if capture_stderr && capture_stderr != :merge_to_stdout
+      else
+        signal = /mswin|mingw/ =~ RUBY_PLATFORM ? :KILL : :TERM
+        case pgroup = opt[:pgroup]
+        when 0, true
+          pgroup = -pid
+        when nil, false
+          pgroup = pid
+        end
+        begin
+          Process.kill signal, pgroup
+          Timeout.timeout((reprieve unless signal == :KILL)) do
+            Process.wait(pid)
+          end
+        rescue Errno::ESRCH
+          break
+        rescue Timeout::Error
+          raise if signal == :KILL
+          signal = :KILL
+        else
+          break
+        end while true
+        bt = caller_locations
+        raise Timeout::Error, "execution of #{bt.shift.label} expired", bt.map(&:to_s)
+      end
+      out_p.close if capture_stdout
+      err_p.close if capture_stderr && capture_stderr != :merge_to_stdout
+      Process.wait pid
+      status = $?
+      stdout = stdout_filter.call(stdout) if stdout_filter
+      stderr = stderr_filter.call(stderr) if stderr_filter
+      return stdout, stderr, status
+    end
+  ensure
+    [th_stdout, th_stderr].each do |th|
+      th.kill if th
+    end
+    [in_c, in_p, out_c, out_p, err_c, err_p].each do |io|
+      io.close if io && !io.closed?
+    end
+    [th_stdout, th_stderr].each do |th|
+      th.join if th
+    end
+  end
+  module_function :invoke_ruby
+
+  def suppress_warning
+    verbose, $VERBOSE = $VERBOSE, nil
+    yield
+  ensure
+    $VERBOSE = verbose
+  end
+  module_function :suppress_warning
+
+  if /darwin/ =~ RUBY_PLATFORM
+    DIAGNOSTIC_REPORTS_PATH = File.expand_path("~/Library/Logs/DiagnosticReports")
+    DIAGNOSTIC_REPORTS_TIMEFORMAT = '%Y-%m-%d-%H%M%S'
+    def self.diagnostic_reports(signame, cmd, pid, now)
+      return unless %w[ABRT QUIT SEGV ILL].include?(signame)
+      cmd = File.basename(cmd)
+      path = DIAGNOSTIC_REPORTS_PATH
+      timeformat = DIAGNOSTIC_REPORTS_TIMEFORMAT
+      pat = "#{path}/#{cmd}_#{now.strftime(timeformat)}[-_]*.crash"
+      first = true
+      30.times do
+        first ? (first = false) : sleep(0.1)
+        Dir.glob(pat) do |name|
+          log = File.read(name) rescue next
+          if /\AProcess:\s+#{cmd} \[#{pid}\]$/ =~ log
+            File.unlink(name)
+            File.unlink("#{path}/.#{File.basename(name)}.plist") rescue nil
+            return log
+          end
+        end
+      end
+      nil
+    end
+  else
+    def self.diagnostic_reports(signame, cmd, pid, now)
+    end
+  end
+end
+
+module Test
+  module Unit
+    module Assertions
+      FailDesc = proc do |status, message = "", out = ""|
+        pid = status.pid
+        now = Time.now
+        faildesc = proc do
+          if signo = status.termsig
+            signame = Signal.signame(signo)
+            sigdesc = "signal #{signo}"
+          end
+          log = EnvUtil.diagnostic_reports(signame, EnvUtil.rubybin, pid, now)
+          if signame
+            sigdesc = "SIG#{signame} (#{sigdesc})"
+          end
+          if status.coredump?
+            sigdesc << " (core dumped)"
+          end
+          full_message = ''
+          if message and !message.empty?
+            full_message << message << "\n"
+          end
+          full_message << "pid #{pid} killed by #{sigdesc}"
+          if out and !out.empty?
+            full_message << "\n#{out.gsub(/^/, '| ')}"
+            full_message << "\n" if /\n\z/ !~ full_message
+          end
+          if log
+            full_message << "\n#{log.gsub(/^/, '| ')}"
+          end
+          full_message
+        end
+        faildesc
+      end
+
+      ABORT_SIGNALS = Signal.list.values_at(*%w"ILL ABRT BUS SEGV")
+
+      def assert_separately(args, file = nil, line = nil, src, ignore_stderr: nil, **opt)
+        unless file and line
+          loc, = caller_locations(1,1)
+          file ||= loc.path
+          line ||= loc.lineno
+        end
+        line -= 6 # lines until src
+        src = <<eom
+# -*- coding: #{src.encoding}; -*-
+  require 'test/unit';include Test::Unit::Assertions
+  END {
+    puts [Marshal.dump($!)].pack('m')#, "assertions=\#{self._assertions}"
+    exit
+  }
+  def pend(msg = nil) $stdout.syswrite [Marshal.dump(msg.to_s)].pack("m"); exit! 0 end
+#{src}
+  class Test::Unit::Runner
+    @@stop_auto_run = true
+  end
+eom
+        args = args.dup
+        args.insert((Hash === args.first ? 1 : 0), "--disable=gems", *$:.map {|l| "-I#{l}"})
+        stdout, stderr, status = EnvUtil.invoke_ruby(args, src, true, true, **opt)
+        abort = status.coredump? || (status.signaled? && ABORT_SIGNALS.include?(status.termsig))
+        assert(!abort, FailDesc[status, nil, stderr])
+        #self._assertions += stdout[/^assertions=(\d+)/, 1].to_i
+        begin
+          res = Marshal.load(stdout.unpack("m")[0])
+        rescue => marshal_error
+          ignore_stderr = nil
+        end
+        if res.is_a?(String)
+          pend res
+        elsif res
+          if bt = res.backtrace
+            bt.each do |l|
+              l.sub!(/\A-:(\d+)/){"#{file}:#{line + $1.to_i}"}
+            end
+            bt.concat(caller)
+          else
+            res.set_backtrace(caller)
+          end
+          raise res
+        end
+
+        # really is it succeed?
+        unless ignore_stderr
+          # the body of assert_separately must not output anything to detect error
+          assert_equal("", stderr, "assert_separately failed with error message")
+        end
+        assert_equal(0, status, "assert_separately failed: '#{stderr}'")
+        raise marshal_error if marshal_error
+      end
+
+      def message msg = nil, ending = ".", &default
+        proc {
+          msg = msg.call.chomp(".") if Proc === msg
+          custom_message = "#{msg}.\n" unless msg.nil? or msg.to_s.empty?
+          "#{custom_message}#{default.call}#{ending}"
+        }
+      end
+
+      # threads should respond to shift method.
+      # Array can be used.
+      def assert_join_threads(threads, message = nil)
+        errs = []
+        values = []
+        while th = threads.shift
+          begin
+            values << th.value
+          rescue Exception
+            errs << [th, $!]
+          end
+        end
+        if !errs.empty?
+          msg = "exceptions on #{errs.length} threads:\n" +
+            errs.map {|t, err|
+            "#{t.inspect}:\n" +
+            err.backtrace.map.with_index {|line, i|
+              if i == 0
+                "#{line}: #{err.message} (#{err.class})"
+              else
+                "\tfrom #{line}"
+              end
+            }.join("\n")
+          }.join("\n---\n")
+          if message
+            msg = "#{message}\n#{msg}"
+          end
+          raise Test::Unit::AssertionFailedError, msg
+        end
+        values
+      end
+
+      def mu_pp(obj) #:nodoc:
+        obj.pretty_inspect.chomp
+      end
+
+      # :call-seq:
+      #   assert_raise_with_message(exception, expected, msg = nil, &block)
+      #
+      #Tests if the given block raises an exception with the expected
+      #message.
+      #
+      #    assert_raise_with_message(RuntimeError, "foo") do
+      #      nil #Fails, no Exceptions are raised
+      #    end
+      #
+      #    assert_raise_with_message(RuntimeError, "foo") do
+      #      raise ArgumentError, "foo" #Fails, different Exception is raised
+      #    end
+      #
+      #    assert_raise_with_message(RuntimeError, "foo") do
+      #      raise "bar" #Fails, RuntimeError is raised but the message differs
+      #    end
+      #
+      #    assert_raise_with_message(RuntimeError, "foo") do
+      #      raise "foo" #Raises RuntimeError with the message, so assertion succeeds
+      #    end
+      def assert_raise_with_message(exception, expected, msg = nil, &block)
+        case expected
+        when String
+          assert = :assert_equal
+        when Regexp
+          assert = :assert_match
+        else
+          raise TypeError, "Expected #{expected.inspect} to be a kind of String or Regexp, not #{expected.class}"
+        end
+
+        ex = m = nil
+        ex = assert_raise(exception, msg || "Exception(#{exception}) with message matches to #{expected.inspect}") do
+          yield
+        end
+        m = ex.message
+        msg = message(msg, "") {"Expected Exception(#{exception}) was raised, but the message doesn't match"}
+
+        if assert == :assert_equal
+          assert_equal(expected, m, msg)
+        else
+          msg = message(msg) { "Expected #{mu_pp expected} to match #{mu_pp m}" }
+          assert expected =~ m, msg
+          block.binding.eval("proc{|_|$~=_}").call($~)
+        end
+        ex
+      end
+    end
+  end
+end
diff --git a/test/test_asn1.rb b/test/test_asn1.rb
new file mode 100644
index 0000000..91ae2cf
--- /dev/null
+++ b/test/test_asn1.rb
@@ -0,0 +1,586 @@
+# frozen_string_literal: false
+require_relative 'utils'
+
+class  OpenSSL::TestASN1 < OpenSSL::TestCase
+  def test_decode
+    subj = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=TestCA")
+    key = OpenSSL::TestUtils::TEST_KEY_RSA1024
+    now = Time.at(Time.now.to_i) # suppress usec
+    s = 0xdeadbeafdeadbeafdeadbeafdeadbeaf
+    exts = [
+      ["basicConstraints","CA:TRUE,pathlen:1",true],
+      ["keyUsage","keyCertSign, cRLSign",true],
+      ["subjectKeyIdentifier","hash",false],
+    ]
+    dgst = OpenSSL::Digest::SHA1.new
+    cert = OpenSSL::TestUtils.issue_cert(
+      subj, key, s, exts, nil, nil, digest: dgst, not_before: now, not_after: now+3600)
+
+
+    asn1 = OpenSSL::ASN1.decode(cert)
+    assert_equal(OpenSSL::ASN1::Sequence, asn1.class)
+    assert_equal(3, asn1.value.size)
+    tbs_cert, sig_alg, sig_val = *asn1.value
+
+    assert_equal(OpenSSL::ASN1::Sequence, tbs_cert.class)
+    assert_equal(8, tbs_cert.value.size)
+
+    version = tbs_cert.value[0]
+    assert_equal(:CONTEXT_SPECIFIC, version.tag_class)
+    assert_equal(0, version.tag)
+    assert_equal(1, version.value.size)
+    assert_equal(OpenSSL::ASN1::Integer, version.value[0].class)
+    assert_equal(2, version.value[0].value)
+
+    serial = tbs_cert.value[1]
+    assert_equal(OpenSSL::ASN1::Integer, serial.class)
+    assert_equal(0xdeadbeafdeadbeafdeadbeafdeadbeaf, serial.value)
+
+    sig = tbs_cert.value[2]
+    assert_equal(OpenSSL::ASN1::Sequence, sig.class)
+    assert_equal(2, sig.value.size)
+    assert_equal(OpenSSL::ASN1::ObjectId, sig.value[0].class)
+    assert_equal("1.2.840.113549.1.1.5", sig.value[0].oid)
+    assert_equal(OpenSSL::ASN1::Null, sig.value[1].class)
+
+    dn = tbs_cert.value[3] # issuer
+    assert_equal(subj.hash, OpenSSL::X509::Name.new(dn).hash)
+    assert_equal(OpenSSL::ASN1::Sequence, dn.class)
+    assert_equal(3, dn.value.size)
+    assert_equal(OpenSSL::ASN1::Set, dn.value[0].class)
+    assert_equal(OpenSSL::ASN1::Set, dn.value[1].class)
+    assert_equal(OpenSSL::ASN1::Set, dn.value[2].class)
+    assert_equal(1, dn.value[0].value.size)
+    assert_equal(1, dn.value[1].value.size)
+    assert_equal(1, dn.value[2].value.size)
+    assert_equal(OpenSSL::ASN1::Sequence, dn.value[0].value[0].class)
+    assert_equal(OpenSSL::ASN1::Sequence, dn.value[1].value[0].class)
+    assert_equal(OpenSSL::ASN1::Sequence, dn.value[2].value[0].class)
+    assert_equal(2, dn.value[0].value[0].value.size)
+    assert_equal(2, dn.value[1].value[0].value.size)
+    assert_equal(2, dn.value[2].value[0].value.size)
+    oid, value = *dn.value[0].value[0].value
+    assert_equal(OpenSSL::ASN1::ObjectId, oid.class)
+    assert_equal("0.9.2342.19200300.100.1.25", oid.oid)
+    assert_equal(OpenSSL::ASN1::IA5String, value.class)
+    assert_equal("org", value.value)
+    oid, value = *dn.value[1].value[0].value
+    assert_equal(OpenSSL::ASN1::ObjectId, oid.class)
+    assert_equal("0.9.2342.19200300.100.1.25", oid.oid)
+    assert_equal(OpenSSL::ASN1::IA5String, value.class)
+    assert_equal("ruby-lang", value.value)
+    oid, value = *dn.value[2].value[0].value
+    assert_equal(OpenSSL::ASN1::ObjectId, oid.class)
+    assert_equal("2.5.4.3", oid.oid)
+    assert_equal(OpenSSL::ASN1::UTF8String, value.class)
+    assert_equal("TestCA", value.value)
+
+    validity = tbs_cert.value[4]
+    assert_equal(OpenSSL::ASN1::Sequence, validity.class)
+    assert_equal(2, validity.value.size)
+    assert_equal(OpenSSL::ASN1::UTCTime, validity.value[0].class)
+    assert_equal(now, validity.value[0].value)
+    assert_equal(OpenSSL::ASN1::UTCTime, validity.value[1].class)
+    assert_equal(now+3600, validity.value[1].value)
+
+    dn = tbs_cert.value[5] # subject
+    assert_equal(subj.hash, OpenSSL::X509::Name.new(dn).hash)
+    assert_equal(OpenSSL::ASN1::Sequence, dn.class)
+    assert_equal(3, dn.value.size)
+    assert_equal(OpenSSL::ASN1::Set, dn.value[0].class)
+    assert_equal(OpenSSL::ASN1::Set, dn.value[1].class)
+    assert_equal(OpenSSL::ASN1::Set, dn.value[2].class)
+    assert_equal(1, dn.value[0].value.size)
+    assert_equal(1, dn.value[1].value.size)
+    assert_equal(1, dn.value[2].value.size)
+    assert_equal(OpenSSL::ASN1::Sequence, dn.value[0].value[0].class)
+    assert_equal(OpenSSL::ASN1::Sequence, dn.value[1].value[0].class)
+    assert_equal(OpenSSL::ASN1::Sequence, dn.value[2].value[0].class)
+    assert_equal(2, dn.value[0].value[0].value.size)
+    assert_equal(2, dn.value[1].value[0].value.size)
+    assert_equal(2, dn.value[2].value[0].value.size)
+    oid, value = *dn.value[0].value[0].value
+    assert_equal(OpenSSL::ASN1::ObjectId, oid.class)
+    assert_equal("0.9.2342.19200300.100.1.25", oid.oid)
+    assert_equal(OpenSSL::ASN1::IA5String, value.class)
+    assert_equal("org", value.value)
+    oid, value = *dn.value[1].value[0].value
+    assert_equal(OpenSSL::ASN1::ObjectId, oid.class)
+    assert_equal("0.9.2342.19200300.100.1.25", oid.oid)
+    assert_equal(OpenSSL::ASN1::IA5String, value.class)
+    assert_equal("ruby-lang", value.value)
+    oid, value = *dn.value[2].value[0].value
+    assert_equal(OpenSSL::ASN1::ObjectId, oid.class)
+    assert_equal("2.5.4.3", oid.oid)
+    assert_equal(OpenSSL::ASN1::UTF8String, value.class)
+    assert_equal("TestCA", value.value)
+
+    pkey = tbs_cert.value[6]
+    assert_equal(OpenSSL::ASN1::Sequence, pkey.class)
+    assert_equal(2, pkey.value.size)
+    assert_equal(OpenSSL::ASN1::Sequence, pkey.value[0].class)
+    assert_equal(2, pkey.value[0].value.size)
+    assert_equal(OpenSSL::ASN1::ObjectId, pkey.value[0].value[0].class)
+    assert_equal("1.2.840.113549.1.1.1", pkey.value[0].value[0].oid)
+    assert_equal(OpenSSL::ASN1::BitString, pkey.value[1].class)
+    assert_equal(0, pkey.value[1].unused_bits)
+    spkey = OpenSSL::ASN1.decode(pkey.value[1].value)
+    assert_equal(OpenSSL::ASN1::Sequence, spkey.class)
+    assert_equal(2, spkey.value.size)
+    assert_equal(OpenSSL::ASN1::Integer, spkey.value[0].class)
+    assert_equal(143085709396403084580358323862163416700436550432664688288860593156058579474547937626086626045206357324274536445865308750491138538454154232826011964045825759324933943290377903384882276841880081931690695505836279972214003660451338124170055999155993192881685495391496854691199517389593073052473319331505702779271, spkey.value[0].value)
+    assert_equal(OpenSSL::ASN1::Integer, spkey.value[1].class)
+    assert_equal(65537, spkey.value[1].value)
+
+    extensions = tbs_cert.value[7]
+    assert_equal(:CONTEXT_SPECIFIC, extensions.tag_class)
+    assert_equal(3, extensions.tag)
+    assert_equal(1, extensions.value.size)
+    assert_equal(OpenSSL::ASN1::Sequence, extensions.value[0].class)
+    assert_equal(3, extensions.value[0].value.size)
+
+    ext = extensions.value[0].value[0]  # basicConstraints
+    assert_equal(OpenSSL::ASN1::Sequence, ext.class)
+    assert_equal(3, ext.value.size)
+    assert_equal(OpenSSL::ASN1::ObjectId, ext.value[0].class)
+    assert_equal("2.5.29.19",  ext.value[0].oid)
+    assert_equal(OpenSSL::ASN1::Boolean, ext.value[1].class)
+    assert_equal(true, ext.value[1].value)
+    assert_equal(OpenSSL::ASN1::OctetString, ext.value[2].class)
+    extv = OpenSSL::ASN1.decode(ext.value[2].value)
+    assert_equal(OpenSSL::ASN1::Sequence, extv.class)
+    assert_equal(2, extv.value.size)
+    assert_equal(OpenSSL::ASN1::Boolean, extv.value[0].class)
+    assert_equal(true, extv.value[0].value)
+    assert_equal(OpenSSL::ASN1::Integer, extv.value[1].class)
+    assert_equal(1, extv.value[1].value)
+
+    ext = extensions.value[0].value[1]  # keyUsage
+    assert_equal(OpenSSL::ASN1::Sequence, ext.class)
+    assert_equal(3, ext.value.size)
+    assert_equal(OpenSSL::ASN1::ObjectId, ext.value[0].class)
+    assert_equal("2.5.29.15",  ext.value[0].oid)
+    assert_equal(OpenSSL::ASN1::Boolean, ext.value[1].class)
+    assert_equal(true, ext.value[1].value)
+    assert_equal(OpenSSL::ASN1::OctetString, ext.value[2].class)
+    extv = OpenSSL::ASN1.decode(ext.value[2].value)
+    assert_equal(OpenSSL::ASN1::BitString, extv.class)
+    str = "\000"; str[0] = 0b00000110.chr
+    assert_equal(str, extv.value)
+
+    ext = extensions.value[0].value[2]  # subjetKeyIdentifier
+    assert_equal(OpenSSL::ASN1::Sequence, ext.class)
+    assert_equal(2, ext.value.size)
+    assert_equal(OpenSSL::ASN1::ObjectId, ext.value[0].class)
+    assert_equal("2.5.29.14",  ext.value[0].oid)
+    assert_equal(OpenSSL::ASN1::OctetString, ext.value[1].class)
+    extv = OpenSSL::ASN1.decode(ext.value[1].value)
+    assert_equal(OpenSSL::ASN1::OctetString, extv.class)
+    sha1 = OpenSSL::Digest::SHA1.new
+    sha1.update(pkey.value[1].value)
+    assert_equal(sha1.digest, extv.value)
+
+    assert_equal(OpenSSL::ASN1::Sequence, sig_alg.class)
+    assert_equal(2, sig_alg.value.size)
+    assert_equal(OpenSSL::ASN1::ObjectId, pkey.value[0].value[0].class)
+    assert_equal("1.2.840.113549.1.1.1", pkey.value[0].value[0].oid)
+    assert_equal(OpenSSL::ASN1::Null, pkey.value[0].value[1].class)
+
+    assert_equal(OpenSSL::ASN1::BitString, sig_val.class)
+    cululated_sig = key.sign(OpenSSL::Digest::SHA1.new, tbs_cert.to_der)
+    assert_equal(cululated_sig, sig_val.value)
+  end
+
+  def test_encode_boolean
+    encode_decode_test(OpenSSL::ASN1::Boolean, [true, false])
+  end
+
+  def test_encode_integer
+    encode_decode_test(OpenSSL::ASN1::Integer, [72, -127, -128, 128, -1, 0, 1, -(2**12345), 2**12345])
+  end
+
+  def test_encode_nil
+    m = OpenSSL::ASN1
+    [
+      m::Boolean, m::Integer, m::BitString, m::OctetString,
+      m::ObjectId, m::Enumerated, m::UTF8String, m::UTCTime,
+      m::GeneralizedTime, m::Sequence, m::Set
+    ].each do |klass|
+      #Primitives raise TypeError, Constructives NoMethodError
+      assert_raise(TypeError, NoMethodError) { klass.send(:new, nil).to_der }
+    end
+  end
+
+  def encode_decode_test(type, values)
+    values.each do |v|
+      assert_equal(v, OpenSSL::ASN1.decode(type.new(v).to_der).value)
+    end
+  end
+
+  def test_decode_pem #should fail gracefully (cf. [ruby-dev:44542])
+    pem = <<-_EOS_
+-----BEGIN CERTIFICATE-----
+MIIC8zCCAdugAwIBAgIBATANBgkqhkiG9w0BAQUFADA9MRMwEQYKCZImiZPyLGQB
+GRYDb3JnMRkwFwYKCZImiZPyLGQBGRYJcnVieS1sYW5nMQswCQYDVQQDDAJDQTAe
+Fw0xMTA5MjUxMzQ4MjZaFw0xMTA5MjUxNDQ4MjZaMD0xEzARBgoJkiaJk/IsZAEZ
+FgNvcmcxGTAXBgoJkiaJk/IsZAEZFglydWJ5LWxhbmcxCzAJBgNVBAMMAkNBMIIB
+IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuV9ht9J7k4NBs38jOXvvTKY9
+gW8nLICSno5EETR1cuF7i4pNs9I1QJGAFAX0BEO4KbzXmuOvfCpD3CU+Slp1enen
+fzq/t/e/1IRW0wkJUJUFQign4CtrkJL+P07yx18UjyPlBXb81ApEmAB5mrJVSrWm
+qbjs07JbuS4QQGGXLc+Su96DkYKmSNVjBiLxVVSpyZfAY3hD37d60uG+X8xdW5v6
+8JkRFIhdGlb6JL8fllf/A/blNwdJOhVr9mESHhwGjwfSeTDPfd8ZLE027E5lyAVX
+9KZYcU00mOX+fdxOSnGqS/8JDRh0EPHDL15RcJjV2J6vZjPb0rOYGDoMcH+94wID
+AQABMA0GCSqGSIb3DQEBBQUAA4IBAQAiAtrIr1pLX4GYN5klviWKb8HC9ICYuAFI
+NfE3FwqzErEVXotuMe3yPVyB3Bv6rjYY/x5EtS5+WPTbHlvHZTkfcsnTpizcn4mW
+dJ6dDRaFCHt1YKKjUxqBt9lvvrc3nReYZN/P+s1mrDhWzGf8iPZgf8sFUHgnaK7W
+CXRVXmPFgCDRNpDDVQ0MQkr509yYfTH+dujNzqTCwSvkyZFyQ7Oe8Yj0VR6kquG3
+rEzBQ0F9dUyqQ9gyRg8KHhDfv9HzT1d/rnUZMkoombwYBRIUChGCYV0GnJcan2Zm
+/93PnPG1IvPjYNd5VlV+sXSnaxQn974HRCsMv7jA8BD6IgSaX6WK
+-----END CERTIFICATE-----
+    _EOS_
+    assert_raise(OpenSSL::ASN1::ASN1Error) { OpenSSL::ASN1.decode(pem) }
+    assert_raise(OpenSSL::ASN1::ASN1Error) { OpenSSL::ASN1.decode_all(pem) }
+  end
+
+  def test_primitive_cannot_set_infinite_length
+    prim = OpenSSL::ASN1::Integer.new(50)
+    assert_equal false, prim.infinite_length
+    assert_not_respond_to prim, :infinite_length=
+  end
+
+  def test_decode_all
+    expected = %w{ 02 01 01 02 01 02 02 01 03 }
+    raw = [expected.join('')].pack('H*')
+    ary = OpenSSL::ASN1.decode_all(raw)
+    assert_equal(3, ary.size)
+    ary.each_with_index do |asn1, i|
+      assert_universal(OpenSSL::ASN1::INTEGER, asn1)
+      assert_equal(i + 1, asn1.value)
+    end
+  end
+
+  def test_decode_utctime
+    expected = Time.at 1374535380
+    assert_equal expected, OpenSSL::ASN1.decode("\x17\v1307222323Z").value
+
+    expected += 17
+    assert_equal expected, OpenSSL::ASN1.decode("\x17\r130722232317Z").value
+  end
+
+  def test_encode_utctime_2k38
+    encoded = OpenSSL::ASN1::UTCTime(2 ** 31 - 1).to_der
+    assert_equal 2 ** 31 - 1, OpenSSL::ASN1.decode(encoded).value.to_i
+
+    encoded = OpenSSL::ASN1::UTCTime(2 ** 31).to_der
+    assert_equal 2 ** 31, OpenSSL::ASN1.decode(encoded).value.to_i
+  end
+
+  def test_decode_generalisedtime
+    expected = Time.at 1481225640
+    assert_equal expected, OpenSSL::ASN1.decode("\x18\x0D201612081934Z").value
+
+    expected += 29
+    assert_equal expected, OpenSSL::ASN1.decode("\x18\x0F20161208193429Z").value
+  end
+
+  def test_decode_enumerated
+    encoded = OpenSSL::ASN1.Enumerated(0).to_der
+    assert_equal "\x0a\x01\x00".b, encoded
+    assert_equal encoded, OpenSSL::ASN1.decode(encoded).to_der
+  end
+
+  def test_create_inf_length_primitive
+    expected = %w{ 24 80 04 01 61 00 00 }
+    raw = [expected.join('')].pack('H*')
+    content = [OpenSSL::ASN1::OctetString.new("a"), OpenSSL::ASN1::EndOfContent.new]
+    cons = OpenSSL::ASN1::Constructive.new(content, OpenSSL::ASN1::OCTET_STRING, nil, :UNIVERSAL)
+    cons.infinite_length = true
+    assert_equal(nil, cons.tagging)
+    assert_equal(raw, cons.to_der)
+    asn1 = OpenSSL::ASN1.decode(raw)
+    assert(asn1.infinite_length)
+    assert_equal(raw, asn1.to_der)
+  end
+
+  def test_cons_without_inf_length_forbidden
+    assert_raise(OpenSSL::ASN1::ASN1Error) do
+      val = OpenSSL::ASN1::OctetString.new('a')
+      cons = OpenSSL::ASN1::Constructive.new([val], OpenSSL::ASN1::OCTET_STRING, nil, :UNIVERSAL)
+      cons.to_der
+    end
+  end
+
+  def test_cons_without_array_forbidden
+    assert_raise(OpenSSL::ASN1::ASN1Error) do
+      val = OpenSSL::ASN1::OctetString.new('a')
+      cons = OpenSSL::ASN1::Constructive.new(val, OpenSSL::ASN1::OCTET_STRING, nil, :UNIVERSAL)
+      cons.infinite_length = true
+      cons.to_der
+    end
+  end
+
+  def test_parse_empty_sequence
+    expected = %w{ A0 07 30 02 30 00 02 01 00 }
+    raw = [expected.join('')].pack('H*')
+    asn1 = OpenSSL::ASN1.decode(raw)
+    assert_equal(raw, asn1.to_der)
+    assert_equal(2, asn1.value.size)
+    seq = asn1.value[0]
+    assert_equal(1, seq.value.size)
+    inner_seq = seq.value[0]
+    assert_equal(0, inner_seq.value.size)
+  end
+
+  def test_parse_tagged_0_infinite
+    expected = %w{ 30 80 02 01 01 80 01 02 00 00 }
+    raw = [expected.join('')].pack('H*')
+    asn1 = OpenSSL::ASN1.decode(raw)
+    assert_equal(3, asn1.value.size)
+    int = asn1.value[0]
+    assert_universal(OpenSSL::ASN1::INTEGER, int)
+    tagged = asn1.value[1]
+    assert_equal(0, tagged.tag)
+    assert_universal(OpenSSL::ASN1::EOC, asn1.value[2])
+    assert_equal(raw, asn1.to_der)
+  end
+
+  def test_seq_infinite_length
+    content = [ OpenSSL::ASN1::Null.new(nil),
+                OpenSSL::ASN1::EndOfContent.new ]
+    cons = OpenSSL::ASN1::Sequence.new(content)
+    cons.infinite_length = true
+    expected = %w{ 30 80 05 00 00 00 }
+    raw = [expected.join('')].pack('H*')
+    assert_equal(raw, cons.to_der)
+    assert_equal(raw, OpenSSL::ASN1.decode(raw).to_der)
+  end
+
+  def test_set_infinite_length
+    content = [ OpenSSL::ASN1::Null.new(nil),
+                OpenSSL::ASN1::EndOfContent.new() ]
+    cons = OpenSSL::ASN1::Set.new(content)
+    cons.infinite_length = true
+    expected = %w{ 31 80 05 00 00 00 }
+    raw = [expected.join('')].pack('H*')
+    assert_equal(raw, cons.to_der)
+    assert_equal(raw, OpenSSL::ASN1.decode(raw).to_der)
+  end
+
+  def test_octet_string_infinite_length
+    octets = [ OpenSSL::ASN1::OctetString.new('aaa'),
+               OpenSSL::ASN1::EndOfContent.new() ]
+    cons = OpenSSL::ASN1::Constructive.new(octets, OpenSSL::ASN1::OCTET_STRING, nil, :UNIVERSAL)
+    cons.infinite_length = true
+    expected = %w{ 24 80 04 03 61 61 61 00 00 }
+    raw = [expected.join('')].pack('H*')
+    assert_equal(raw, cons.to_der)
+    assert_equal(raw, OpenSSL::ASN1.decode(raw).to_der)
+  end
+
+  def test_prim_explicit_tagging
+    oct_str = OpenSSL::ASN1::OctetString.new("a", 0, :EXPLICIT)
+    expected = %w{ A0 03 04 01 61 }
+    raw = [expected.join('')].pack('H*')
+    assert_equal(raw, oct_str.to_der)
+    assert_equal(raw, OpenSSL::ASN1.decode(raw).to_der)
+  end
+
+  def test_prim_explicit_tagging_tag_class
+    oct_str = OpenSSL::ASN1::OctetString.new("a", 0, :EXPLICIT)
+    oct_str2 = OpenSSL::ASN1::OctetString.new("a", 0, :EXPLICIT, :CONTEXT_SPECIFIC)
+    assert_equal(oct_str.to_der, oct_str2.to_der)
+  end
+
+  def test_prim_implicit_tagging
+    int = OpenSSL::ASN1::Integer.new(1, 0, :IMPLICIT)
+    expected = %w{ 80 01 01 }
+    raw = [expected.join('')].pack('H*')
+    assert_equal(raw, int.to_der)
+    assert_equal(raw, OpenSSL::ASN1.decode(raw).to_der)
+  end
+
+  def test_prim_implicit_tagging_tag_class
+    int = OpenSSL::ASN1::Integer.new(1, 0, :IMPLICIT)
+    int2 = OpenSSL::ASN1::Integer.new(1, 0, :IMPLICIT, :CONTEXT_SPECIFIC);
+    assert_equal(int.to_der, int2.to_der)
+  end
+
+  def test_cons_explicit_tagging
+    content = [ OpenSSL::ASN1::PrintableString.new('abc') ]
+    seq = OpenSSL::ASN1::Sequence.new(content, 2, :EXPLICIT)
+    expected = %w{ A2 07 30 05 13 03 61 62 63 }
+    raw = [expected.join('')].pack('H*')
+    assert_equal(raw, seq.to_der)
+    assert_equal(raw, OpenSSL::ASN1.decode(raw).to_der)
+  end
+
+  def test_cons_explicit_tagging_inf_length
+    content = [ OpenSSL::ASN1::PrintableString.new('abc') ,
+                OpenSSL::ASN1::EndOfContent.new() ]
+    seq = OpenSSL::ASN1::Sequence.new(content, 2, :EXPLICIT)
+    seq.infinite_length = true
+    expected = %w{ A2 80 30 80 13 03 61 62 63 00 00 00 00 }
+    raw = [expected.join('')].pack('H*')
+    assert_equal(raw, seq.to_der)
+    assert_equal(raw, OpenSSL::ASN1.decode(raw).to_der)
+  end
+
+  def test_cons_implicit_tagging
+    content = [ OpenSSL::ASN1::Null.new(nil) ]
+    seq = OpenSSL::ASN1::Sequence.new(content, 1, :IMPLICIT)
+    expected = %w{ A1 02 05 00 }
+    raw = [expected.join('')].pack('H*')
+    assert_equal(raw, seq.to_der)
+    assert_equal(raw, OpenSSL::ASN1.decode(raw).to_der)
+  end
+
+  def test_cons_implicit_tagging_inf_length
+    content = [ OpenSSL::ASN1::Null.new(nil),
+                OpenSSL::ASN1::EndOfContent.new() ]
+    seq = OpenSSL::ASN1::Sequence.new(content, 1, :IMPLICIT)
+    seq.infinite_length = true
+    expected = %w{ A1 80 05 00 00 00 }
+    raw = [expected.join('')].pack('H*')
+    assert_equal(raw, seq.to_der)
+    assert_equal(raw, OpenSSL::ASN1.decode(raw).to_der)
+  end
+
+  def test_octet_string_infinite_length_explicit_tagging
+    octets = [ OpenSSL::ASN1::OctetString.new('aaa'),
+               OpenSSL::ASN1::EndOfContent.new() ]
+    cons = OpenSSL::ASN1::Constructive.new(octets, 1, :EXPLICIT)
+    cons.infinite_length = true
+    expected = %w{ A1 80 24 80 04 03 61 61 61 00 00 00 00 }
+    raw = [expected.join('')].pack('H*')
+    assert_equal(raw, cons.to_der)
+    assert_equal(raw, OpenSSL::ASN1.decode(raw).to_der)
+  end
+
+  def test_octet_string_infinite_length_implicit_tagging
+    octets = [ OpenSSL::ASN1::OctetString.new('aaa'),
+               OpenSSL::ASN1::EndOfContent.new() ]
+    cons = OpenSSL::ASN1::Constructive.new(octets, 0, :IMPLICIT)
+    cons.infinite_length = true
+    expected = %w{ A0 80 04 03 61 61 61 00 00 }
+    raw = [expected.join('')].pack('H*')
+    assert_equal(raw, cons.to_der)
+    assert_equal(raw, OpenSSL::ASN1.decode(raw).to_der)
+  end
+
+  def test_recursive_octet_string_infinite_length
+    octets_sub1 = [ OpenSSL::ASN1::OctetString.new("\x01"),
+                    OpenSSL::ASN1::EndOfContent.new() ]
+    octets_sub2 = [ OpenSSL::ASN1::OctetString.new("\x02"),
+                    OpenSSL::ASN1::EndOfContent.new() ]
+    container1 = OpenSSL::ASN1::Constructive.new(octets_sub1, OpenSSL::ASN1::OCTET_STRING, nil, :UNIVERSAL)
+    container1.infinite_length = true
+    container2 = OpenSSL::ASN1::Constructive.new(octets_sub2, OpenSSL::ASN1::OCTET_STRING, nil, :UNIVERSAL)
+    container2.infinite_length = true
+    octets3 = OpenSSL::ASN1::OctetString.new("\x03")
+
+    octets = [ container1, container2, octets3,
+               OpenSSL::ASN1::EndOfContent.new() ]
+    cons = OpenSSL::ASN1::Constructive.new(octets, OpenSSL::ASN1::OCTET_STRING, nil, :UNIVERSAL)
+    cons.infinite_length = true
+    expected = %w{ 24 80 24 80 04 01 01 00 00 24 80 04 01 02 00 00 04 01 03 00 00 }
+    raw = [expected.join('')].pack('H*')
+    assert_equal(raw, cons.to_der)
+    assert_equal(raw, OpenSSL::ASN1.decode(raw).to_der)
+  end
+
+  def test_bit_string_infinite_length
+    content = [ OpenSSL::ASN1::BitString.new("\x01"),
+                OpenSSL::ASN1::EndOfContent.new() ]
+    cons = OpenSSL::ASN1::Constructive.new(content, OpenSSL::ASN1::BIT_STRING, nil, :UNIVERSAL)
+    cons.infinite_length = true
+    expected = %w{ 23 80 03 02 00 01 00 00 }
+    raw = [expected.join('')].pack('H*')
+    assert_equal(raw, cons.to_der)
+    assert_equal(raw, OpenSSL::ASN1.decode(raw).to_der)
+  end
+
+  def test_primitive_inf_length
+    assert_raise(OpenSSL::ASN1::ASN1Error) do
+      spec = %w{ 02 80 02 01 01 00 00 }
+      raw = [spec.join('')].pack('H*')
+      OpenSSL::ASN1.decode(raw)
+      OpenSSL::ASN1.decode_all(raw)
+    end
+  end
+
+  def test_recursive_octet_string_parse
+    test = %w{ 24 80 24 80 04 01 01 00 00 24 80 04 01 02 00 00 04 01 03 00 00 }
+    raw = [test.join('')].pack('H*')
+    asn1 = OpenSSL::ASN1.decode(raw)
+    assert_equal(OpenSSL::ASN1::Constructive, asn1.class)
+    assert_universal(OpenSSL::ASN1::OCTET_STRING, asn1)
+    assert_equal(true, asn1.infinite_length)
+    assert_equal(4, asn1.value.size)
+    nested1 = asn1.value[0]
+    assert_equal(OpenSSL::ASN1::Constructive, nested1.class)
+    assert_universal(OpenSSL::ASN1::OCTET_STRING, nested1)
+    assert_equal(true, nested1.infinite_length)
+    assert_equal(2, nested1.value.size)
+    oct1 = nested1.value[0]
+    assert_universal(OpenSSL::ASN1::OCTET_STRING, oct1)
+    assert_equal(false, oct1.infinite_length)
+    assert_universal(OpenSSL::ASN1::EOC, nested1.value[1])
+    assert_equal(false, nested1.value[1].infinite_length)
+    nested2 = asn1.value[1]
+    assert_equal(OpenSSL::ASN1::Constructive, nested2.class)
+    assert_universal(OpenSSL::ASN1::OCTET_STRING, nested2)
+    assert_equal(true, nested2.infinite_length)
+    assert_equal(2, nested2.value.size)
+    oct2 = nested2.value[0]
+    assert_universal(OpenSSL::ASN1::OCTET_STRING, oct2)
+    assert_equal(false, oct2.infinite_length)
+    assert_universal(OpenSSL::ASN1::EOC, nested2.value[1])
+    assert_equal(false, nested2.value[1].infinite_length)
+    oct3 = asn1.value[2]
+    assert_universal(OpenSSL::ASN1::OCTET_STRING, oct3)
+    assert_equal(false, oct3.infinite_length)
+    assert_universal(OpenSSL::ASN1::EOC, asn1.value[3])
+    assert_equal(false, asn1.value[3].infinite_length)
+  end
+
+  def test_decode_constructed_overread
+    test = %w{ 31 06 31 02 30 02 05 00 }
+    #                          ^ <- invalid
+    raw = [test.join].pack("H*")
+    ret = []
+    assert_raise(OpenSSL::ASN1::ASN1Error) {
+      OpenSSL::ASN1.traverse(raw) { |x| ret << x }
+    }
+    assert_equal 2, ret.size
+    assert_equal 17, ret[0][6]
+    assert_equal 17, ret[1][6]
+
+    test = %w{ 31 80 30 03 00 00 }
+    #                    ^ <- invalid
+    raw = [test.join].pack("H*")
+    ret = []
+    assert_raise(OpenSSL::ASN1::ASN1Error) {
+      OpenSSL::ASN1.traverse(raw) { |x| ret << x }
+    }
+    assert_equal 1, ret.size
+    assert_equal 17, ret[0][6]
+  end
+
+  def test_constructive_each
+    data = [OpenSSL::ASN1::Integer.new(0), OpenSSL::ASN1::Integer.new(1)]
+    seq = OpenSSL::ASN1::Sequence.new data
+
+    assert_equal data, seq.entries
+  end
+
+  private
+
+  def assert_universal(tag, asn1)
+    assert_equal(tag, asn1.tag)
+    if asn1.respond_to?(:tagging)
+      assert_nil(asn1.tagging)
+    end
+    assert_equal(:UNIVERSAL, asn1.tag_class)
+  end
+
+end if defined?(OpenSSL::TestUtils)
diff --git a/test/test_bn.rb b/test/test_bn.rb
new file mode 100644
index 0000000..37ba5e5
--- /dev/null
+++ b/test/test_bn.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: false
+require_relative 'utils'
+
+if defined?(OpenSSL::TestUtils)
+
+class OpenSSL::TestBN < OpenSSL::TestCase
+  def test_new_str
+    e1 = OpenSSL::BN.new(999.to_s(16), 16) # OpenSSL::BN.new(str, 16) must be most stable
+    e2 = OpenSSL::BN.new((2**107-1).to_s(16), 16)
+    assert_equal(e1, OpenSSL::BN.new("999"))
+    assert_equal(e2, OpenSSL::BN.new((2**107-1).to_s))
+    assert_equal(e1, OpenSSL::BN.new("999", 10))
+    assert_equal(e2, OpenSSL::BN.new((2**107-1).to_s, 10))
+    assert_equal(e1, OpenSSL::BN.new("\x03\xE7", 2))
+    assert_equal(e2, OpenSSL::BN.new("\a\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 2))
+    assert_equal(e1, OpenSSL::BN.new("\x00\x00\x00\x02\x03\xE7", 0))
+    assert_equal(e2, OpenSSL::BN.new("\x00\x00\x00\x0E\a\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 0))
+  end
+
+  def test_new_bn
+    e1 = OpenSSL::BN.new(999.to_s(16), 16)
+    e2 = OpenSSL::BN.new((2**107-1).to_s(16), 16)
+    assert_equal(e1, OpenSSL::BN.new(e1))
+    assert_equal(e2, OpenSSL::BN.new(e2))
+  end
+
+  def test_new_integer
+    assert_equal(999.to_bn, OpenSSL::BN.new(999))
+    assert_equal((2 ** 107 - 1).to_bn, OpenSSL::BN.new(2 ** 107 - 1))
+    assert_equal(-999.to_bn, OpenSSL::BN.new(-999))
+    assert_equal((-(2 ** 107 - 1)).to_bn, OpenSSL::BN.new(-(2 ** 107 - 1)))
+  end
+
+  def test_to_bn
+    e1 = OpenSSL::BN.new(999.to_s(16), 16)
+    e2 = OpenSSL::BN.new((2**107-1).to_s(16), 16)
+    assert_equal(e1, 999.to_bn)
+    assert_equal(e2, (2**107-1).to_bn)
+  end
+
+  def test_prime_p
+    assert_equal(true, OpenSSL::BN.new((2 ** 107 - 1).to_s(16), 16).prime?)
+    assert_equal(true, OpenSSL::BN.new((2 ** 127 - 1).to_s(16), 16).prime?(1))
+  end
+
+  def test_cmp
+    bn1 = OpenSSL::BN.new('1')
+    bn2 = OpenSSL::BN.new('1')
+    bn3 = OpenSSL::BN.new('2')
+    assert_equal(false, bn1 == nil)
+    assert_equal(true,  bn1 != nil)
+    assert_equal(true, bn1 == bn2)
+    assert_equal(false, bn1 == bn3)
+    assert_equal(true, bn1.eql?(bn2))
+    assert_equal(false, bn1.eql?(bn3))
+    assert_equal(bn1.hash, bn2.hash)
+    assert_not_equal(bn3.hash, bn1.hash)
+  end
+end
+
+end
diff --git a/test/test_buffering.rb b/test/test_buffering.rb
new file mode 100644
index 0000000..f85353f
--- /dev/null
+++ b/test/test_buffering.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: false
+require_relative 'utils'
+require 'stringio'
+
+class OpenSSL::TestBuffering < OpenSSL::TestCase
+
+  class IO
+    include OpenSSL::Buffering
+
+    attr_accessor :sync
+
+    def initialize
+      @io = ""
+      def @io.sync
+        true
+      end
+
+      super
+
+      @sync = false
+    end
+
+    def string
+      @io
+    end
+
+    def sysread(size)
+      str = @io.slice!(0, size)
+      raise EOFError if str.empty?
+      str
+    end
+
+    def syswrite(str)
+      @io << str
+      str.size
+    end
+  end
+
+  def setup
+    super
+    @io = IO.new
+  end
+
+  def test_flush
+    @io.write 'a'
+
+    assert_not_predicate @io, :sync
+    assert_empty @io.string
+
+    assert_equal @io, @io.flush
+
+    assert_not_predicate @io, :sync
+    assert_equal 'a', @io.string
+  end
+
+  def test_flush_error
+    @io.write 'a'
+
+    assert_not_predicate @io, :sync
+    assert_empty @io.string
+
+    def @io.syswrite *a
+      raise SystemCallError, 'fail'
+    end
+
+    assert_raise SystemCallError do
+      @io.flush
+    end
+
+    assert_not_predicate @io, :sync, 'sync must not change'
+  end
+
+  def test_getc
+    @io.syswrite('abc')
+    assert_equal(?a, @io.getc)
+    assert_equal(?b, @io.getc)
+    assert_equal(?c, @io.getc)
+  end
+
+  def test_each_byte
+    @io.syswrite('abc')
+    res = []
+    @io.each_byte do |c|
+      res << c
+    end
+    assert_equal([97, 98, 99], res)
+  end
+
+end if defined?(OpenSSL::TestUtils)
diff --git a/test/test_cipher.rb b/test/test_cipher.rb
new file mode 100644
index 0000000..ad0e87b
--- /dev/null
+++ b/test/test_cipher.rb
@@ -0,0 +1,318 @@
+# frozen_string_literal: false
+require_relative 'utils'
+
+if defined?(OpenSSL::TestUtils)
+
+class OpenSSL::TestCipher < OpenSSL::TestCase
+  module Helper
+    def has_cipher?(name)
+      @ciphers ||= OpenSSL::Cipher.ciphers
+      @ciphers.include?(name)
+    end
+  end
+  include Helper
+  extend Helper
+
+  def test_encrypt_decrypt
+    # NIST SP 800-38A F.2.1
+    key = ["2b7e151628aed2a6abf7158809cf4f3c"].pack("H*")
+    iv =  ["000102030405060708090a0b0c0d0e0f"].pack("H*")
+    pt =  ["6bc1bee22e409f96e93d7e117393172a" \
+           "ae2d8a571e03ac9c9eb76fac45af8e51"].pack("H*")
+    ct =  ["7649abac8119b246cee98e9b12e9197d" \
+           "5086cb9b507219ee95db113a917678b2"].pack("H*")
+    cipher = new_encryptor("aes-128-cbc", key: key, iv: iv, padding: 0)
+    assert_equal ct, cipher.update(pt) << cipher.final
+    cipher = new_decryptor("aes-128-cbc", key: key, iv: iv, padding: 0)
+    assert_equal pt, cipher.update(ct) << cipher.final
+  end
+
+  def test_pkcs5_keyivgen
+    pass = "\x00" * 8
+    salt = "\x01" * 8
+    num = 2048
+    pt = "data to be encrypted"
+    cipher = OpenSSL::Cipher.new("DES-EDE3-CBC").encrypt
+    cipher.pkcs5_keyivgen(pass, salt, num, "MD5")
+    s1 = cipher.update(pt) << cipher.final
+
+    d1 = num.times.inject(pass + salt) {|out, _| OpenSSL::Digest::MD5.digest(out) }
+    d2 = num.times.inject(d1 + pass + salt) {|out, _| OpenSSL::Digest::MD5.digest(out) }
+    key = (d1 + d2)[0, 24]
+    iv = (d1 + d2)[24, 8]
+    cipher = new_encryptor("DES-EDE3-CBC", key: key, iv: iv)
+    s2 = cipher.update(pt) << cipher.final
+
+    assert_equal s1, s2
+  end
+
+  def test_info
+    cipher = OpenSSL::Cipher.new("DES-EDE3-CBC").encrypt
+    assert_equal "DES-EDE3-CBC", cipher.name
+    assert_equal 24, cipher.key_len
+    assert_equal 8, cipher.iv_len
+  end
+
+  def test_dup
+    cipher = OpenSSL::Cipher.new("aes-128-cbc").encrypt
+    assert_equal cipher.name, cipher.dup.name
+    cipher.encrypt
+    cipher.random_key
+    cipher.random_iv
+    tmpc = cipher.dup
+    s1 = cipher.update("data") + cipher.final
+    s2 = tmpc.update("data") + tmpc.final
+    assert_equal(s1, s2, "encrypt dup")
+  end
+
+  def test_reset
+    cipher = OpenSSL::Cipher.new("aes-128-cbc").encrypt
+    cipher.encrypt
+    cipher.random_key
+    cipher.random_iv
+    s1 = cipher.update("data") + cipher.final
+    cipher.reset
+    s2 = cipher.update("data") + cipher.final
+    assert_equal(s1, s2, "encrypt reset")
+  end
+
+  def test_key_iv_set
+    cipher = OpenSSL::Cipher.new("DES-EDE3-CBC").encrypt
+    assert_raise(ArgumentError) { cipher.key = "\x01" * 23 }
+    assert_nothing_raised { cipher.key = "\x01" * 24 }
+    assert_raise(ArgumentError) { cipher.key = "\x01" * 25 }
+    assert_raise(ArgumentError) { cipher.iv = "\x01" * 7 }
+    assert_nothing_raised { cipher.iv = "\x01" * 8 }
+    assert_raise(ArgumentError) { cipher.iv = "\x01" * 9 }
+  end
+
+  def test_random_key_iv
+    data = "data"
+    s1, s2 = 2.times.map do
+      cipher = OpenSSL::Cipher.new("aes-128-cbc").encrypt
+      cipher.random_key
+      cipher.iv = "\x01" * 16
+      cipher.update(data) << cipher.final
+    end
+    assert_not_equal s1, s2
+
+    s1, s2 = 2.times.map do
+      cipher = OpenSSL::Cipher.new("aes-128-cbc").encrypt
+      cipher.key = "\x01" * 16
+      cipher.random_iv
+      cipher.update(data) << cipher.final
+    end
+    assert_not_equal s1, s2
+  end
+
+  def test_empty_data
+    cipher = OpenSSL::Cipher.new("DES-EDE3-CBC").encrypt
+    cipher.random_key
+    assert_raise(ArgumentError) { cipher.update("") }
+  end
+
+  def test_initialize
+    cipher = OpenSSL::Cipher.new("DES-EDE3-CBC")
+    assert_raise(RuntimeError) { cipher.__send__(:initialize, "DES-EDE3-CBC") }
+    assert_raise(RuntimeError) { OpenSSL::Cipher.allocate.final }
+  end
+
+  def test_ctr_if_exists
+    # NIST SP 800-38A F.5.1
+    key = ["2b7e151628aed2a6abf7158809cf4f3c"].pack("H*")
+    iv =  ["f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"].pack("H*")
+    pt =  ["6bc1bee22e409f96e93d7e117393172a" \
+           "ae2d8a571e03ac9c9eb76fac45af8e51"].pack("H*")
+    ct =  ["874d6191b620e3261bef6864990db6ce" \
+           "9806f66b7970fdff8617187bb9fffdff"].pack("H*")
+    cipher = new_encryptor("aes-128-ctr", key: key, iv: iv, padding: 0)
+    assert_equal ct, cipher.update(pt) << cipher.final
+    cipher = new_decryptor("aes-128-ctr", key: key, iv: iv, padding: 0)
+    assert_equal pt, cipher.update(ct) << cipher.final
+  end if has_cipher?('aes-128-ctr')
+
+  def test_ciphers
+    OpenSSL::Cipher.ciphers.each{|name|
+      next if /netbsd/ =~ RUBY_PLATFORM && /idea|rc5/i =~ name
+      begin
+        assert_kind_of(OpenSSL::Cipher, OpenSSL::Cipher.new(name))
+      rescue OpenSSL::Cipher::CipherError => e
+        raise unless /wrap/ =~ name and /wrap mode not allowed/ =~ e.message
+      end
+    }
+  end
+
+  def test_AES
+    pt = File.read(__FILE__)
+    %w(ECB CBC CFB OFB).each{|mode|
+      c1 = OpenSSL::Cipher::AES256.new(mode)
+      c1.encrypt
+      c1.pkcs5_keyivgen("passwd")
+      ct = c1.update(pt) + c1.final
+
+      c2 = OpenSSL::Cipher::AES256.new(mode)
+      c2.decrypt
+      c2.pkcs5_keyivgen("passwd")
+      assert_equal(pt, c2.update(ct) + c2.final)
+    }
+  end
+
+  def test_update_raise_if_key_not_set
+    assert_raise(OpenSSL::Cipher::CipherError) do
+      # it caused OpenSSL SEGV by uninitialized key [Bug #2768]
+      OpenSSL::Cipher::AES128.new("ECB").update "." * 17
+    end
+  end
+
+  def test_authenticated
+    if has_cipher?('aes-128-gcm')
+      cipher = OpenSSL::Cipher.new('aes-128-gcm')
+      assert_predicate(cipher, :authenticated?)
+    end
+    cipher = OpenSSL::Cipher.new('aes-128-cbc')
+    assert_not_predicate(cipher, :authenticated?)
+  end
+
+  def test_aes_gcm
+    # GCM spec Appendix B Test Case 4
+    key = ["feffe9928665731c6d6a8f9467308308"].pack("H*")
+    iv =  ["cafebabefacedbaddecaf888"].pack("H*")
+    aad = ["feedfacedeadbeeffeedfacedeadbeef" \
+           "abaddad2"].pack("H*")
+    pt =  ["d9313225f88406e5a55909c5aff5269a" \
+           "86a7a9531534f7da2e4c303d8a318a72" \
+           "1c3c0c95956809532fcf0e2449a6b525" \
+           "b16aedf5aa0de657ba637b39"].pack("H*")
+    ct =  ["42831ec2217774244b7221b784d0d49c" \
+           "e3aa212f2c02a4e035c17e2329aca12e" \
+           "21d514b25466931c7d8f6a5aac84aa05" \
+           "1ba30b396a0aac973d58e091"].pack("H*")
+    tag = ["5bc94fbc3221a5db94fae95ae7121a47"].pack("H*")
+
+    cipher = new_encryptor("aes-128-gcm", key: key, iv: iv, auth_data: aad)
+    assert_equal ct, cipher.update(pt) << cipher.final
+    assert_equal tag, cipher.auth_tag
+    cipher = new_decryptor("aes-128-gcm", key: key, iv: iv, auth_tag: tag, auth_data: aad)
+    assert_equal pt, cipher.update(ct) << cipher.final
+
+    # truncated tag is accepted
+    cipher = new_encryptor("aes-128-gcm", key: key, iv: iv, auth_data: aad)
+    assert_equal ct, cipher.update(pt) << cipher.final
+    assert_equal tag[0, 8], cipher.auth_tag(8)
+    cipher = new_decryptor("aes-128-gcm", key: key, iv: iv, auth_tag: tag[0, 8], auth_data: aad)
+    assert_equal pt, cipher.update(ct) << cipher.final
+
+    # wrong tag is rejected
+    tag2 = tag.dup
+    tag2.setbyte(-1, (tag2.getbyte(-1) + 1) & 0xff)
+    cipher = new_decryptor("aes-128-gcm", key: key, iv: iv, auth_tag: tag2, auth_data: aad)
+    cipher.update(ct)
+    assert_raise(OpenSSL::Cipher::CipherError) { cipher.final }
+
+    # wrong aad is rejected
+    aad2 = aad[0..-2] << aad[-1].succ
+    cipher = new_decryptor("aes-128-gcm", key: key, iv: iv, auth_tag: tag, auth_data: aad2)
+    cipher.update(ct)
+    assert_raise(OpenSSL::Cipher::CipherError) { cipher.final }
+
+    # wrong ciphertext is rejected
+    ct2 = ct[0..-2] << ct[-1].succ
+    cipher = new_decryptor("aes-128-gcm", key: key, iv: iv, auth_tag: tag, auth_data: aad)
+    cipher.update(ct2)
+    assert_raise(OpenSSL::Cipher::CipherError) { cipher.final }
+  end if has_cipher?("aes-128-gcm")
+
+  def test_aes_gcm_variable_iv_len
+    # GCM spec Appendix B Test Case 5
+    key = ["feffe9928665731c6d6a8f9467308308"].pack("H*")
+    iv  = ["cafebabefacedbad"].pack("H*")
+    aad = ["feedfacedeadbeeffeedfacedeadbeef" \
+           "abaddad2"].pack("H*")
+    pt =  ["d9313225f88406e5a55909c5aff5269a" \
+           "86a7a9531534f7da2e4c303d8a318a72" \
+           "1c3c0c95956809532fcf0e2449a6b525" \
+           "b16aedf5aa0de657ba637b39"].pack("H*")
+    ct =  ["61353b4c2806934a777ff51fa22a4755" \
+           "699b2a714fcdc6f83766e5f97b6c7423" \
+           "73806900e49f24b22b097544d4896b42" \
+           "4989b5e1ebac0f07c23f4598"].pack("H*")
+    tag = ["3612d2e79e3b0785561be14aaca2fccb"].pack("H*")
+
+    cipher = new_encryptor("aes-128-gcm", key: key, iv_len: 8, iv: iv, auth_data: aad)
+    assert_equal ct, cipher.update(pt) << cipher.final
+    assert_equal tag, cipher.auth_tag
+    cipher = new_decryptor("aes-128-gcm", key: key, iv_len: 8, iv: iv, auth_tag: tag, auth_data: aad)
+    assert_equal pt, cipher.update(ct) << cipher.final
+  end if has_cipher?("aes-128-gcm")
+
+  def test_aes_ocb_tag_len
+    # RFC 7253 Appendix A; the second sample
+    key = ["000102030405060708090A0B0C0D0E0F"].pack("H*")
+    iv  = ["BBAA99887766554433221101"].pack("H*")
+    aad = ["0001020304050607"].pack("H*")
+    pt =  ["0001020304050607"].pack("H*")
+    ct =  ["6820B3657B6F615A"].pack("H*")
+    tag = ["5725BDA0D3B4EB3A257C9AF1F8F03009"].pack("H*")
+
+    cipher = new_encryptor("aes-128-ocb", key: key, iv: iv, auth_data: aad)
+    assert_equal ct, cipher.update(pt) << cipher.final
+    assert_equal tag, cipher.auth_tag
+    cipher = new_decryptor("aes-128-ocb", key: key, iv: iv, auth_tag: tag, auth_data: aad)
+    assert_equal pt, cipher.update(ct) << cipher.final
+
+    # RFC 7253 Appendix A; with 96 bits tag length
+    key = ["0F0E0D0C0B0A09080706050403020100"].pack("H*")
+    iv  = ["BBAA9988776655443322110D"].pack("H*")
+    aad = ["000102030405060708090A0B0C0D0E0F1011121314151617" \
+           "18191A1B1C1D1E1F2021222324252627"].pack("H*")
+    pt =  ["000102030405060708090A0B0C0D0E0F1011121314151617" \
+           "18191A1B1C1D1E1F2021222324252627"].pack("H*")
+    ct =  ["1792A4E31E0755FB03E31B22116E6C2DDF9EFD6E33D536F1" \
+           "A0124B0A55BAE884ED93481529C76B6A"].pack("H*")
+    tag = ["D0C515F4D1CDD4FDAC4F02AA"].pack("H*")
+
+    cipher = new_encryptor("aes-128-ocb", auth_tag_len: 12, key: key, iv: iv, auth_data: aad)
+    assert_equal ct, cipher.update(pt) << cipher.final
+    assert_equal tag, cipher.auth_tag
+    cipher = new_decryptor("aes-128-ocb", auth_tag_len: 12, key: key, iv: iv, auth_tag: tag, auth_data: aad)
+    assert_equal pt, cipher.update(ct) << cipher.final
+
+  end if has_cipher?("aes-128-ocb")
+
+  def test_aes_gcm_key_iv_order_issue
+    pt = "[ruby/openssl#49]"
+    cipher = OpenSSL::Cipher.new("aes-128-gcm").encrypt
+    cipher.key = "x" * 16
+    cipher.iv = "a" * 12
+    ct1 = cipher.update(pt) << cipher.final
+    tag1 = cipher.auth_tag
+
+    cipher = OpenSSL::Cipher.new("aes-128-gcm").encrypt
+    cipher.iv = "a" * 12
+    cipher.key = "x" * 16
+    ct2 = cipher.update(pt) << cipher.final
+    tag2 = cipher.auth_tag
+
+    assert_equal ct1, ct2
+    assert_equal tag1, tag2
+  end if has_cipher?("aes-128-gcm")
+
+  private
+
+  def new_encryptor(algo, **kwargs)
+    OpenSSL::Cipher.new(algo).tap do |cipher|
+      cipher.encrypt
+      kwargs.each {|k, v| cipher.send(:"#{k}=", v) }
+    end
+  end
+
+  def new_decryptor(algo, **kwargs)
+    OpenSSL::Cipher.new(algo).tap do |cipher|
+      cipher.decrypt
+      kwargs.each {|k, v| cipher.send(:"#{k}=", v) }
+    end
+  end
+
+end
+
+end
diff --git a/test/test_config.rb b/test/test_config.rb
new file mode 100644
index 0000000..786bce9
--- /dev/null
+++ b/test/test_config.rb
@@ -0,0 +1,300 @@
+# frozen_string_literal: false
+require_relative 'utils'
+
+class OpenSSL::TestConfig < OpenSSL::TestCase
+  def setup
+    super
+    file = Tempfile.open("openssl.cnf")
+    file << <<__EOD__
+HOME = .
+[ ca ]
+default_ca = CA_default
+[ CA_default ]
+dir = ./demoCA
+certs                =                  ./certs
+__EOD__
+    file.close
+    @tmpfile = file
+    @it = OpenSSL::Config.new(file.path)
+  end
+
+  def teardown
+    super
+    @tmpfile.close!
+  end
+
+  def test_constants
+    assert(defined?(OpenSSL::Config::DEFAULT_CONFIG_FILE))
+    config_file = OpenSSL::Config::DEFAULT_CONFIG_FILE
+    pend "DEFAULT_CONFIG_FILE may return a wrong path on your platforms. [Bug #6830]" unless File.readable?(config_file)
+    assert_nothing_raised do
+      OpenSSL::Config.load(config_file)
+    end
+  end
+
+  def test_s_parse
+    c = OpenSSL::Config.parse('')
+    assert_equal("[ default ]\n\n", c.to_s)
+    c = OpenSSL::Config.parse(@it.to_s)
+    assert_equal(['CA_default', 'ca', 'default'], c.sections.sort)
+  end
+
+  def test_s_parse_format
+    c = OpenSSL::Config.parse(<<__EOC__)
+ baz =qx\t                # "baz = qx"
+
+foo::bar = baz            # shortcut section::key format
+  default::bar = baz      # ditto
+a=\t \t                   # "a = ": trailing spaces are ignored
+ =b                       # " = b": empty key
+ =c                       # " = c": empty key (override the above line)
+    d=                    # "c = ": trailing comment is ignored
+
+sq = 'foo''b\\'ar'
+    dq ="foo""''\\""
+    dq2 = foo""bar
+esc=a\\r\\n\\b\\tb
+foo\\bar = foo\\b\\\\ar
+foo\\bar::foo\\bar = baz
+[default1  default2]\t\t  # space is allowed in section name
+          fo =b  ar       # space allowed in value
+[emptysection]
+ [doller ]
+foo=bar
+bar = $(foo)
+baz = 123$(default::bar)456${foo}798
+qux = ${baz}
+quxx = $qux.$qux
+__EOC__
+    assert_equal(['default', 'default1  default2', 'doller', 'emptysection', 'foo', 'foo\\bar'], c.sections.sort)
+    assert_equal(['', 'a', 'bar', 'baz', 'd', 'dq', 'dq2', 'esc', 'foo\\bar', 'sq'], c['default'].keys.sort)
+    assert_equal('c', c['default'][''])
+    assert_equal('', c['default']['a'])
+    assert_equal('qx', c['default']['baz'])
+    assert_equal('', c['default']['d'])
+    assert_equal('baz', c['default']['bar'])
+    assert_equal("foob'ar", c['default']['sq'])
+    assert_equal("foo''\"", c['default']['dq'])
+    assert_equal("foobar", c['default']['dq2'])
+    assert_equal("a\r\n\b\tb", c['default']['esc'])
+    assert_equal("foo\b\\ar", c['default']['foo\\bar'])
+    assert_equal('baz', c['foo']['bar'])
+    assert_equal('baz', c['foo\\bar']['foo\\bar'])
+    assert_equal('b  ar', c['default1  default2']['fo'])
+
+    # dolloer
+    assert_equal('bar', c['doller']['foo'])
+    assert_equal('bar', c['doller']['bar'])
+    assert_equal('123baz456bar798', c['doller']['baz'])
+    assert_equal('123baz456bar798', c['doller']['qux'])
+    assert_equal('123baz456bar798.123baz456bar798', c['doller']['quxx'])
+
+    excn = assert_raise(OpenSSL::ConfigError) do
+      OpenSSL::Config.parse("foo = $bar")
+    end
+    assert_equal("error in line 1: variable has no value", excn.message)
+
+    excn = assert_raise(OpenSSL::ConfigError) do
+      OpenSSL::Config.parse("foo = $(bar")
+    end
+    assert_equal("error in line 1: no close brace", excn.message)
+
+    excn = assert_raise(OpenSSL::ConfigError) do
+      OpenSSL::Config.parse("f o =b  ar      # no space in key")
+    end
+    assert_equal("error in line 1: missing equal sign", excn.message)
+
+    excn = assert_raise(OpenSSL::ConfigError) do
+      OpenSSL::Config.parse(<<__EOC__)
+# comment 1               # comments
+
+#
+ # comment 2
+\t#comment 3
+  [second    ]\t
+[third                    # section not terminated
+__EOC__
+    end
+    assert_equal("error in line 7: missing close square bracket", excn.message)
+  end
+
+  def test_s_load
+    # alias of new
+    c = OpenSSL::Config.load
+    assert_equal("", c.to_s)
+    assert_equal([], c.sections)
+    #
+    Tempfile.create("openssl.cnf") {|file|
+      file.close
+      c = OpenSSL::Config.load(file.path)
+      assert_equal("[ default ]\n\n", c.to_s)
+      assert_equal(['default'], c.sections)
+    }
+  end
+
+  def test_initialize
+    c = OpenSSL::Config.new
+    assert_equal("", c.to_s)
+    assert_equal([], c.sections)
+  end
+
+  def test_initialize_with_empty_file
+    Tempfile.create("openssl.cnf") {|file|
+      file.close
+      c = OpenSSL::Config.new(file.path)
+      assert_equal("[ default ]\n\n", c.to_s)
+      assert_equal(['default'], c.sections)
+    }
+  end
+
+  def test_initialize_with_example_file
+    assert_equal(['CA_default', 'ca', 'default'], @it.sections.sort)
+  end
+
+  def test_get_value
+    assert_equal('CA_default', @it.get_value('ca', 'default_ca'))
+    assert_equal(nil, @it.get_value('ca', 'no such key'))
+    assert_equal(nil, @it.get_value('no such section', 'no such key'))
+    assert_equal('.', @it.get_value('', 'HOME'))
+    assert_raise(TypeError) do
+      @it.get_value(nil, 'HOME') # not allowed unlike Config#value
+    end
+    # fallback to 'default' ugly...
+    assert_equal('.', @it.get_value('unknown', 'HOME'))
+  end
+
+  def test_get_value_ENV
+    key = ENV.keys.first
+    assert_not_nil(key) # make sure we have at least one ENV var.
+    assert_equal(ENV[key], @it.get_value('ENV', key))
+  end
+
+  def test_value
+    # suppress deprecation warnings
+    OpenSSL::TestUtils.silent do
+      assert_equal('CA_default', @it.value('ca', 'default_ca'))
+      assert_equal(nil, @it.value('ca', 'no such key'))
+      assert_equal(nil, @it.value('no such section', 'no such key'))
+      assert_equal('.', @it.value('', 'HOME'))
+      assert_equal('.', @it.value(nil, 'HOME'))
+      assert_equal('.', @it.value('HOME'))
+      # fallback to 'default' ugly...
+      assert_equal('.', @it.value('unknown', 'HOME'))
+    end
+  end
+
+  def test_value_ENV
+    OpenSSL::TestUtils.silent do
+      key = ENV.keys.first
+      assert_not_nil(key) # make sure we have at least one ENV var.
+      assert_equal(ENV[key], @it.value('ENV', key))
+    end
+  end
+
+  def test_aref
+    assert_equal({'HOME' => '.'}, @it['default'])
+    assert_equal({'dir' => './demoCA', 'certs' => './certs'}, @it['CA_default'])
+    assert_equal({}, @it['no_such_section'])
+    assert_equal({}, @it[''])
+  end
+
+  def test_section
+    OpenSSL::TestUtils.silent do
+      assert_equal({'HOME' => '.'}, @it.section('default'))
+      assert_equal({'dir' => './demoCA', 'certs' => './certs'}, @it.section('CA_default'))
+      assert_equal({}, @it.section('no_such_section'))
+      assert_equal({}, @it.section(''))
+    end
+  end
+
+  def test_sections
+    assert_equal(['CA_default', 'ca', 'default'], @it.sections.sort)
+    @it['new_section'] = {'foo' => 'bar'}
+    assert_equal(['CA_default', 'ca', 'default', 'new_section'], @it.sections.sort)
+    @it['new_section'] = {}
+    assert_equal(['CA_default', 'ca', 'default', 'new_section'], @it.sections.sort)
+  end
+
+  def test_add_value
+    c = OpenSSL::Config.new
+    assert_equal("", c.to_s)
+    # add key
+    c.add_value('default', 'foo', 'bar')
+    assert_equal("[ default ]\nfoo=bar\n\n", c.to_s)
+    # add another key
+    c.add_value('default', 'baz', 'qux')
+    assert_equal('bar', c['default']['foo'])
+    assert_equal('qux', c['default']['baz'])
+    # update the value
+    c.add_value('default', 'baz', 'quxxx')
+    assert_equal('bar', c['default']['foo'])
+    assert_equal('quxxx', c['default']['baz'])
+    # add section and key
+    c.add_value('section', 'foo', 'bar')
+    assert_equal('bar', c['default']['foo'])
+    assert_equal('quxxx', c['default']['baz'])
+    assert_equal('bar', c['section']['foo'])
+  end
+
+  def test_aset
+    @it['foo'] = {'bar' => 'baz'}
+    assert_equal({'bar' => 'baz'}, @it['foo'])
+    @it['foo'] = {'bar' => 'qux', 'baz' => 'quxx'}
+    assert_equal({'bar' => 'qux', 'baz' => 'quxx'}, @it['foo'])
+
+    # OpenSSL::Config is add only for now.
+    @it['foo'] = {'foo' => 'foo'}
+    assert_equal({'foo' => 'foo', 'bar' => 'qux', 'baz' => 'quxx'}, @it['foo'])
+    # you cannot override or remove any section and key.
+    @it['foo'] = {}
+    assert_equal({'foo' => 'foo', 'bar' => 'qux', 'baz' => 'quxx'}, @it['foo'])
+  end
+
+  def test_each
+    # each returns [section, key, value] array.
+    ary = @it.map { |e| e }.sort { |a, b| a[0] <=> b[0] }
+    assert_equal(4, ary.size)
+    assert_equal('CA_default', ary[0][0])
+    assert_equal('CA_default', ary[1][0])
+    assert_equal(["ca", "default_ca", "CA_default"], ary[2])
+    assert_equal(["default", "HOME", "."], ary[3])
+  end
+
+  def test_to_s
+    c = OpenSSL::Config.parse("[empty]\n")
+    assert_equal("[ default ]\n\n[ empty ]\n\n", c.to_s)
+  end
+
+  def test_inspect
+    assert_match(/#<OpenSSL::Config sections=\[.*\]>/, @it.inspect)
+  end
+
+  def test_freeze
+    c = OpenSSL::Config.new
+    c['foo'] = [['key', 'value']]
+    c.freeze
+
+    bug = '[ruby-core:18377]'
+    # RuntimeError for 1.9, TypeError for 1.8
+    e = assert_raise(TypeError, bug) do
+      c['foo'] = [['key', 'wrong']]
+    end
+    assert_match(/can't modify/, e.message, bug)
+  end
+
+  def test_dup
+    assert(!@it.sections.empty?)
+    c = @it.dup
+    assert_equal(@it.sections.sort, c.sections.sort)
+    @it['newsection'] = {'a' => 'b'}
+    assert_not_equal(@it.sections.sort, c.sections.sort)
+  end
+
+  def test_clone
+    assert(!@it.sections.empty?)
+    c = @it.clone
+    assert_equal(@it.sections.sort, c.sections.sort)
+    @it['newsection'] = {'a' => 'b'}
+    assert_not_equal(@it.sections.sort, c.sections.sort)
+  end
+end if defined?(OpenSSL::TestUtils)
diff --git a/test/test_digest.rb b/test/test_digest.rb
new file mode 100644
index 0000000..9891d99
--- /dev/null
+++ b/test/test_digest.rb
@@ -0,0 +1,134 @@
+# frozen_string_literal: false
+require_relative 'utils'
+
+if defined?(OpenSSL::TestUtils)
+
+class OpenSSL::TestDigest < OpenSSL::TestCase
+  def setup
+    super
+    @d1 = OpenSSL::Digest.new("MD5")
+    @d2 = OpenSSL::Digest::MD5.new
+  end
+
+  def test_digest
+    null_hex = "d41d8cd98f00b204e9800998ecf8427e"
+    null_bin = [null_hex].pack("H*")
+    data = "DATA"
+    hex = "e44f9e348e41cb272efa87387728571b"
+    bin = [hex].pack("H*")
+    assert_equal(null_bin, @d1.digest)
+    assert_equal(null_hex, @d1.hexdigest)
+    @d1 << data
+    assert_equal(bin, @d1.digest)
+    assert_equal(hex, @d1.hexdigest)
+    assert_equal(bin, OpenSSL::Digest::MD5.digest(data))
+    assert_equal(hex, OpenSSL::Digest::MD5.hexdigest(data))
+  end
+
+  def test_eql
+    assert(@d1 == @d2, "==")
+    d = @d1.clone
+    assert(d == @d1, "clone")
+  end
+
+  def test_info
+    assert_equal("MD5", @d1.name, "name")
+    assert_equal("MD5", @d2.name, "name")
+    assert_equal(16, @d1.size, "size")
+  end
+
+  def test_dup
+    @d1.update("DATA")
+    assert_equal(@d1.name, @d1.dup.name, "dup")
+    assert_equal(@d1.name, @d1.clone.name, "clone")
+    assert_equal(@d1.digest, @d1.clone.digest, "clone .digest")
+  end
+
+  def test_reset
+    @d1.update("DATA")
+    dig1 = @d1.digest
+    @d1.reset
+    @d1.update("DATA")
+    dig2 = @d1.digest
+    assert_equal(dig1, dig2, "reset")
+  end
+
+  def test_digest_constants
+    algs = %w(MD4 MD5 RIPEMD160 SHA1)
+    if OpenSSL::OPENSSL_VERSION_NUMBER < 0x10100000
+      algs += %w(DSS1 SHA)
+    end
+    if OpenSSL::OPENSSL_VERSION_NUMBER > 0x00908000
+      algs += %w(SHA224 SHA256 SHA384 SHA512)
+    end
+    algs.each do |alg|
+      assert_not_nil(OpenSSL::Digest.new(alg))
+      klass = OpenSSL::Digest.const_get(alg)
+      assert_not_nil(klass.new)
+    end
+  end
+
+  def test_digest_by_oid_and_name
+    check_digest(OpenSSL::ASN1::ObjectId.new("MD5"))
+    check_digest(OpenSSL::ASN1::ObjectId.new("SHA1"))
+  end
+
+  if OpenSSL::OPENSSL_VERSION_NUMBER > 0x00908000
+    def encode16(str)
+      str.unpack("H*").first
+    end
+
+    def test_098_features
+      sha224_a = "abd37534c7d9a2efb9465de931cd7055ffdb8879563ae98078d6d6d5"
+      sha256_a = "ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb"
+      sha384_a = "54a59b9f22b0b80880d8427e548b7c23abd873486e1f035dce9cd697e85175033caa88e6d57bc35efae0b5afd3145f31"
+      sha512_a = "1f40fc92da241694750979ee6cf582f2d5d7d28e18335de05abc54d0560e0f5302860c652bf08d560252aa5e74210546f369fbbbce8c12cfc7957b2652fe9a75"
+
+      assert_equal(sha224_a, OpenSSL::Digest::SHA224.hexdigest("a"))
+      assert_equal(sha256_a, OpenSSL::Digest::SHA256.hexdigest("a"))
+      assert_equal(sha384_a, OpenSSL::Digest::SHA384.hexdigest("a"))
+      assert_equal(sha512_a, OpenSSL::Digest::SHA512.hexdigest("a"))
+
+      assert_equal(sha224_a, encode16(OpenSSL::Digest::SHA224.digest("a")))
+      assert_equal(sha256_a, encode16(OpenSSL::Digest::SHA256.digest("a")))
+      assert_equal(sha384_a, encode16(OpenSSL::Digest::SHA384.digest("a")))
+      assert_equal(sha512_a, encode16(OpenSSL::Digest::SHA512.digest("a")))
+    end
+
+    def test_digest_by_oid_and_name_sha2
+      check_digest(OpenSSL::ASN1::ObjectId.new("SHA224"))
+      check_digest(OpenSSL::ASN1::ObjectId.new("SHA256"))
+      check_digest(OpenSSL::ASN1::ObjectId.new("SHA384"))
+      check_digest(OpenSSL::ASN1::ObjectId.new("SHA512"))
+    end
+  end
+
+  def test_openssl_digest
+    assert_equal OpenSSL::Digest::MD5, OpenSSL::Digest("MD5")
+
+    assert_raise NameError do
+      OpenSSL::Digest("no such digest")
+    end
+  end
+
+  private
+
+  def check_digest(oid)
+    d = OpenSSL::Digest.new(oid.sn)
+    assert_not_nil(d)
+    d = OpenSSL::Digest.new(oid.ln)
+    assert_not_nil(d)
+    d = OpenSSL::Digest.new(oid.oid)
+    assert_not_nil(d)
+  end
+
+  def libressl?
+    OpenSSL::OPENSSL_VERSION.include?('LibreSSL')
+  end
+
+  def version_since(verary)
+    (OpenSSL::OPENSSL_LIBRARY_VERSION.scan(/\d+/).map(&:to_i) <=> verary) != -1
+  end
+end
+
+end
diff --git a/test/test_engine.rb b/test/test_engine.rb
new file mode 100644
index 0000000..75e45eb
--- /dev/null
+++ b/test/test_engine.rb
@@ -0,0 +1,99 @@
+# frozen_string_literal: false
+require_relative 'utils'
+
+class OpenSSL::TestEngine < OpenSSL::TestCase
+
+  def test_engines_free # [ruby-dev:44173]
+    with_openssl <<-'end;'
+      OpenSSL::Engine.load("openssl")
+      OpenSSL::Engine.engines
+      OpenSSL::Engine.engines
+    end;
+  end
+
+  def test_openssl_engine_builtin
+    with_openssl <<-'end;'
+      orig = OpenSSL::Engine.engines
+      pend "'openssl' is already loaded" if orig.any? { |e| e.id == "openssl" }
+      engine = OpenSSL::Engine.load("openssl")
+      assert_equal(true, engine)
+      assert_equal(1, OpenSSL::Engine.engines.size - orig.size)
+    end;
+  end
+
+  def test_openssl_engine_by_id_string
+    with_openssl <<-'end;'
+      orig = OpenSSL::Engine.engines
+      pend "'openssl' is already loaded" if orig.any? { |e| e.id == "openssl" }
+      engine = get_engine
+      assert_not_nil(engine)
+      assert_equal(1, OpenSSL::Engine.engines.size - orig.size)
+    end;
+  end
+
+  def test_openssl_engine_id_name_inspect
+    with_openssl <<-'end;'
+      engine = get_engine
+      assert_equal("openssl", engine.id)
+      assert_not_nil(engine.name)
+      assert_not_nil(engine.inspect)
+    end;
+  end
+
+  def test_openssl_engine_digest_sha1
+    with_openssl <<-'end;'
+      engine = get_engine
+      digest = engine.digest("SHA1")
+      assert_not_nil(digest)
+      data = "test"
+      assert_equal(OpenSSL::Digest::SHA1.digest(data), digest.digest(data))
+    end;
+  end
+
+  def test_openssl_engine_cipher_rc4
+    with_openssl <<-'end;'
+      begin
+        engine = get_engine
+        algo = "RC4" #AES is not supported by openssl Engine (<=1.0.0e)
+        data = "a" * 1000
+        key = OpenSSL::Random.random_bytes(16)
+        # suppress message from openssl Engine's RC4 cipher [ruby-core:41026]
+        err_back = $stderr.dup
+        $stderr.reopen(IO::NULL)
+        encrypted = crypt_data(data, key, :encrypt) { engine.cipher(algo) }
+        decrypted = crypt_data(encrypted, key, :decrypt) { OpenSSL::Cipher.new(algo) }
+        assert_equal(data, decrypted)
+      ensure
+        if err_back
+          $stderr.reopen(err_back)
+          err_back.close
+        end
+      end
+    end;
+  end
+
+  private
+
+  # this is required because OpenSSL::Engine methods change global state
+  def with_openssl(code)
+    assert_separately([{ "OSSL_MDEBUG" => nil }, "-ropenssl"], <<~"end;")
+      require #{__FILE__.dump}
+      include OpenSSL::TestEngine::Utils
+      #{code}
+    end;
+  end
+
+  module Utils
+    def get_engine
+      OpenSSL::Engine.by_id("openssl")
+    end
+
+    def crypt_data(data, key, mode)
+      cipher = yield
+      cipher.send mode
+      cipher.key = key
+      cipher.update(data) + cipher.final
+    end
+  end
+
+end if defined?(OpenSSL::TestUtils) && defined?(OpenSSL::Engine)
diff --git a/test/test_fips.rb b/test/test_fips.rb
new file mode 100644
index 0000000..534dade
--- /dev/null
+++ b/test/test_fips.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: false
+require_relative 'utils'
+
+if defined?(OpenSSL::TestUtils)
+
+class OpenSSL::TestFIPS < OpenSSL::TestCase
+
+  def test_fips_mode_is_reentrant
+    OpenSSL.fips_mode = false
+    OpenSSL.fips_mode = false
+  end
+
+end
+
+end
diff --git a/test/test_hmac.rb b/test/test_hmac.rb
new file mode 100644
index 0000000..dbde97d
--- /dev/null
+++ b/test/test_hmac.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: false
+require_relative 'utils'
+
+class OpenSSL::TestHMAC < OpenSSL::TestCase
+  def test_hmac
+    # RFC 2202 2. Test Cases for HMAC-MD5
+    hmac = OpenSSL::HMAC.new(["0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"].pack("H*"), "MD5")
+    hmac.update("Hi There")
+    assert_equal ["9294727a3638bb1c13f48ef8158bfc9d"].pack("H*"), hmac.digest
+    assert_equal "9294727a3638bb1c13f48ef8158bfc9d", hmac.hexdigest
+
+    # RFC 4231 4.2. Test Case 1
+    hmac = OpenSSL::HMAC.new(["0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"].pack("H*"), "SHA224")
+    hmac.update("Hi There")
+    assert_equal ["896fb1128abbdf196832107cd49df33f47b4b1169912ba4f53684b22"].pack("H*"), hmac.digest
+    assert_equal "896fb1128abbdf196832107cd49df33f47b4b1169912ba4f53684b22", hmac.hexdigest
+  end
+
+  def test_dup
+    h1 = OpenSSL::HMAC.new("KEY", "MD5")
+    h1.update("DATA")
+    h = h1.dup
+    assert_equal(h1.digest, h.digest, "dup digest")
+  end
+
+  def test_binary_update
+    data = "Lücíllé: Bût... yøü sáîd hé wås âlrîght.\nDr. Físhmån: Yés. Hé's løst hîs léft hånd, sø hé's gøîng tø bé åll rîght"
+    hmac = OpenSSL::HMAC.new("qShkcwN92rsM9nHfdnP4ugcVU2iI7iM/trovs01ZWok", "SHA256")
+    result = hmac.update(data).hexdigest
+    assert_equal "a13984b929a07912e4e21c5720876a8e150d6f67f854437206e7f86547248396", result
+  end
+
+  def test_reset_keep_key
+    h1 = OpenSSL::HMAC.new("KEY", "MD5")
+    first = h1.update("test").hexdigest
+    h1.reset
+    second = h1.update("test").hexdigest
+    assert_equal first, second
+  end
+end if defined?(OpenSSL::TestUtils)
diff --git a/test/test_ns_spki.rb b/test/test_ns_spki.rb
new file mode 100644
index 0000000..ac34613
--- /dev/null
+++ b/test/test_ns_spki.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: false
+require_relative 'utils'
+
+if defined?(OpenSSL::TestUtils)
+
+class OpenSSL::TestNSSPI < OpenSSL::TestCase
+  def setup
+    super
+    # This request data is adopt from the specification of
+    # "Netscape Extensions for User Key Generation".
+    # -- http://wp.netscape.com/eng/security/comm4-keygen.html
+    @b64  = "MIHFMHEwXDANBgkqhkiG9w0BAQEFAANLADBIAkEAnX0TILJrOMUue+PtwBRE6XfV"
+    @b64 << "WtKQbsshxk5ZhcUwcwyvcnIq9b82QhJdoACdD34rqfCAIND46fXKQUnb0mvKzQID"
+    @b64 << "AQABFhFNb3ppbGxhSXNNeUZyaWVuZDANBgkqhkiG9w0BAQQFAANBAAKv2Eex2n/S"
+    @b64 << "r/7iJNroWlSzSMtTiQTEB+ADWHGj9u1xrUrOilq/o2cuQxIfZcNZkYAkWP4DubqW"
+    @b64 << "i0//rgBvmco="
+  end
+
+  def test_build_data
+    key1 = OpenSSL::TestUtils::TEST_KEY_RSA1024
+    key2 = OpenSSL::TestUtils::TEST_KEY_RSA2048
+    spki = OpenSSL::Netscape::SPKI.new
+    spki.challenge = "RandomString"
+    spki.public_key = key1.public_key
+    spki.sign(key1, OpenSSL::Digest::SHA1.new)
+    assert(spki.verify(spki.public_key))
+    assert(spki.verify(key1.public_key))
+    assert(!spki.verify(key2.public_key))
+
+    der = spki.to_der
+    spki = OpenSSL::Netscape::SPKI.new(der)
+    assert_equal("RandomString", spki.challenge)
+    assert_equal(key1.public_key.to_der, spki.public_key.to_der)
+    assert(spki.verify(spki.public_key))
+    assert_not_nil(spki.to_text)
+  end
+
+  def test_decode_data
+    spki = OpenSSL::Netscape::SPKI.new(@b64)
+    assert_equal(@b64, spki.to_pem)
+    assert_equal(@b64.unpack("m").first, spki.to_der)
+    assert_equal("MozillaIsMyFriend", spki.challenge)
+    assert_equal(OpenSSL::PKey::RSA, spki.public_key.class)
+
+    spki = OpenSSL::Netscape::SPKI.new(@b64.unpack("m").first)
+    assert_equal(@b64, spki.to_pem)
+    assert_equal(@b64.unpack("m").first, spki.to_der)
+    assert_equal("MozillaIsMyFriend", spki.challenge)
+    assert_equal(OpenSSL::PKey::RSA, spki.public_key.class)
+  end
+end
+
+end
diff --git a/test/test_ocsp.rb b/test/test_ocsp.rb
new file mode 100644
index 0000000..8881f25
--- /dev/null
+++ b/test/test_ocsp.rb
@@ -0,0 +1,298 @@
+# frozen_string_literal: false
+require_relative "utils"
+
+if defined?(OpenSSL::TestUtils)
+
+class OpenSSL::TestOCSP < OpenSSL::TestCase
+  def setup
+    super
+    # @ca_cert
+    #   |
+    # @cert
+    #   |----------|
+    # @cert2   @ocsp_cert
+
+    ca_subj = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=TestCA")
+    @ca_key = OpenSSL::TestUtils::TEST_KEY_RSA1024
+    ca_exts = [
+      ["basicConstraints", "CA:TRUE", true],
+      ["keyUsage", "cRLSign,keyCertSign", true],
+    ]
+    @ca_cert = OpenSSL::TestUtils.issue_cert(
+      ca_subj, @ca_key, 1, ca_exts, nil, nil)
+
+    cert_subj = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=TestCA2")
+    @cert_key = OpenSSL::TestUtils::TEST_KEY_RSA1024
+    cert_exts = [
+      ["basicConstraints", "CA:TRUE", true],
+      ["keyUsage", "cRLSign,keyCertSign", true],
+    ]
+    @cert = OpenSSL::TestUtils.issue_cert(
+      cert_subj, @cert_key, 5, cert_exts, @ca_cert, @ca_key)
+
+    cert2_subj = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=TestCert")
+    @cert2_key = OpenSSL::TestUtils::TEST_KEY_RSA1024
+    cert2_exts = [
+    ]
+    @cert2 = OpenSSL::TestUtils.issue_cert(
+      cert2_subj, @cert2_key, 10, cert2_exts, @cert, @cert_key)
+
+    ocsp_subj = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=TestCAOCSP")
+    @ocsp_key = OpenSSL::TestUtils::TEST_KEY_RSA2048
+    ocsp_exts = [
+      ["extendedKeyUsage", "OCSPSigning", true],
+    ]
+    @ocsp_cert = OpenSSL::TestUtils.issue_cert(
+       ocsp_subj, @ocsp_key, 100, ocsp_exts, @cert, @cert_key)
+  end
+
+  def test_new_certificate_id
+    cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert)
+    assert_kind_of OpenSSL::OCSP::CertificateId, cid
+    assert_equal @cert.serial, cid.serial
+    cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert, OpenSSL::Digest::SHA256.new)
+    assert_kind_of OpenSSL::OCSP::CertificateId, cid
+    assert_equal @cert.serial, cid.serial
+  end
+
+  def test_certificate_id_issuer_name_hash
+    cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert)
+    assert_equal OpenSSL::Digest::SHA1.hexdigest(@cert.issuer.to_der), cid.issuer_name_hash
+    assert_equal "d91f736ac4dc3242f0fb9b77a3149bd83c5c43d0", cid.issuer_name_hash
+  end
+
+  def test_certificate_id_issuer_key_hash
+    cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert)
+    assert_equal OpenSSL::Digest::SHA1.hexdigest(OpenSSL::ASN1.decode(@ca_cert.to_der).value[0].value[6].value[1].value), cid.issuer_key_hash
+    assert_equal "d1fef9fbf8ae1bc160cbfa03e2596dd873089213", cid.issuer_key_hash
+  end
+
+  def test_certificate_id_hash_algorithm
+    cid_sha1 = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert, OpenSSL::Digest::SHA1.new)
+    cid_sha256 = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert, OpenSSL::Digest::SHA256.new)
+    assert_equal "sha1", cid_sha1.hash_algorithm
+    assert_equal "sha256", cid_sha256.hash_algorithm
+  end
+
+  def test_certificate_id_der
+    cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert)
+    der = cid.to_der
+    asn1 = OpenSSL::ASN1.decode(der)
+    # hash algorithm defaults to SHA-1
+    assert_equal OpenSSL::ASN1.ObjectId("SHA1").to_der, asn1.value[0].value[0].to_der
+    assert_equal [cid.issuer_name_hash].pack("H*"), asn1.value[1].value
+    assert_equal [cid.issuer_key_hash].pack("H*"), asn1.value[2].value
+    assert_equal @cert.serial, asn1.value[3].value
+    assert_equal der, OpenSSL::OCSP::CertificateId.new(der).to_der
+  end
+
+  def test_certificate_id_dup
+    cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert)
+    assert_equal cid.to_der, cid.dup.to_der
+  end
+
+  def test_request_der
+    request = OpenSSL::OCSP::Request.new
+    cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert, OpenSSL::Digest::SHA1.new)
+    request.add_certid(cid)
+    request.sign(@cert, @cert_key, [@ca_cert], 0)
+    asn1 = OpenSSL::ASN1.decode(request.to_der)
+    assert_equal cid.to_der, asn1.value[0].value.find { |a| a.tag_class == :UNIVERSAL }.value[0].value[0].to_der
+    assert_equal OpenSSL::ASN1.ObjectId("sha1WithRSAEncryption").to_der, asn1.value[1].value[0].value[0].value[0].to_der
+    assert_equal @cert.to_der, asn1.value[1].value[0].value[2].value[0].value[0].to_der
+    assert_equal @ca_cert.to_der, asn1.value[1].value[0].value[2].value[0].value[1].to_der
+    assert_equal asn1.to_der, OpenSSL::OCSP::Request.new(asn1.to_der).to_der
+  end
+
+  def test_request_sign_verify
+    cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert)
+    store = OpenSSL::X509::Store.new.add_cert(@ca_cert)
+
+    # with signer cert
+    req = OpenSSL::OCSP::Request.new.add_certid(cid)
+    req.sign(@cert, @cert_key, [])
+    assert_equal true, req.verify([], store)
+
+    # without signer cert
+    req = OpenSSL::OCSP::Request.new.add_certid(cid)
+    req.sign(@cert, @cert_key, nil)
+    assert_equal false, req.verify([@cert2], store)
+    assert_equal false, req.verify([], store) # no signer
+    assert_equal false, req.verify([], store, OpenSSL::OCSP::NOVERIFY)
+
+    assert_equal true, req.verify([@cert], store, OpenSSL::OCSP::NOINTERN)
+    ret = req.verify([@cert], store)
+    if ret || OpenSSL::OPENSSL_VERSION =~ /OpenSSL/ && OpenSSL::OPENSSL_VERSION_NUMBER >= 0x10002000
+      assert_equal true, ret
+    else
+      # RT2560; OCSP_request_verify() does not find signer cert from 'certs' when
+      # OCSP_NOINTERN is not specified.
+      # fixed by OpenSSL 1.0.1j, 1.0.2 and LibreSSL 2.4.2
+      pend "RT2560: ocsp_req_find_signer"
+    end
+  end
+
+  def test_request_nonce
+    req0 = OpenSSL::OCSP::Request.new
+    req1 = OpenSSL::OCSP::Request.new.add_nonce("NONCE")
+    req2 = OpenSSL::OCSP::Request.new.add_nonce("ABCDE")
+    bres = OpenSSL::OCSP::BasicResponse.new
+    assert_equal 2, req0.check_nonce(bres)
+    bres.copy_nonce(req1)
+    assert_equal 3, req0.check_nonce(bres)
+    assert_equal 1, req1.check_nonce(bres)
+    bres.add_nonce("NONCE")
+    assert_equal 1, req1.check_nonce(bres)
+    assert_equal 0, req2.check_nonce(bres)
+  end
+
+  def test_request_dup
+    request = OpenSSL::OCSP::Request.new
+    cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert, OpenSSL::Digest::SHA1.new)
+    request.add_certid(cid)
+    assert_equal request.to_der, request.dup.to_der
+  end
+
+  def test_basic_response_der
+    bres = OpenSSL::OCSP::BasicResponse.new
+    cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert, OpenSSL::Digest::SHA1.new)
+    bres.add_status(cid, OpenSSL::OCSP::V_CERTSTATUS_GOOD, 0, nil, -300, 500, [])
+    bres.add_nonce("NONCE")
+    bres.sign(@ocsp_cert, @ocsp_key, [@ca_cert], 0)
+    der = bres.to_der
+    asn1 = OpenSSL::ASN1.decode(der)
+    assert_equal OpenSSL::ASN1.Sequence([@ocsp_cert, @ca_cert]).to_der, asn1.value[3].value[0].to_der
+    assert_equal der, OpenSSL::OCSP::BasicResponse.new(der).to_der
+  rescue TypeError
+    if /GENERALIZEDTIME/ =~ $!.message
+      pend "OCSP_basic_sign() is broken"
+    else
+      raise
+    end
+  end
+
+  def test_basic_response_sign_verify
+    store = OpenSSL::X509::Store.new.add_cert(@ca_cert)
+
+    # signed by CA
+    bres = OpenSSL::OCSP::BasicResponse.new
+    cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert, "SHA256")
+    bres.add_status(cid, OpenSSL::OCSP::V_CERTSTATUS_GOOD, nil, -400, -300, 500, [])
+    bres.sign(@ca_cert, @ca_key, nil, 0, "SHA256")
+    assert_equal false, bres.verify([], store) # signer not found
+    assert_equal true, bres.verify([@ca_cert], store)
+    bres.sign(@ca_cert, @ca_key, [], 0, "SHA256")
+    assert_equal true, bres.verify([], store)
+
+    # signed by OCSP signer
+    bres = OpenSSL::OCSP::BasicResponse.new
+    cid = OpenSSL::OCSP::CertificateId.new(@cert2, @cert)
+    bres.add_status(cid, OpenSSL::OCSP::V_CERTSTATUS_GOOD, nil, -400, -300, 500, [])
+    bres.sign(@ocsp_cert, @ocsp_key, [@cert])
+    assert_equal true, bres.verify([], store)
+    assert_equal false, bres.verify([], store, OpenSSL::OCSP::NOCHAIN)
+    # OpenSSL had a bug on this; test that our workaround works
+    bres.sign(@ocsp_cert, @ocsp_key, [])
+    assert_equal true, bres.verify([@cert], store)
+  end
+
+  def test_basic_response_dup
+    bres = OpenSSL::OCSP::BasicResponse.new
+    cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert, OpenSSL::Digest::SHA1.new)
+    bres.add_status(cid, OpenSSL::OCSP::V_CERTSTATUS_GOOD, 0, nil, -300, 500, [])
+    bres.sign(@ocsp_cert, @ocsp_key, [@ca_cert], 0)
+    assert_equal bres.to_der, bres.dup.to_der
+  end
+
+  def test_basic_response_response_operations
+    bres = OpenSSL::OCSP::BasicResponse.new
+    now = Time.at(Time.now.to_i)
+    cid1 = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert, OpenSSL::Digest::SHA1.new)
+    cid2 = OpenSSL::OCSP::CertificateId.new(@ocsp_cert, @ca_cert, OpenSSL::Digest::SHA1.new)
+    cid3 = OpenSSL::OCSP::CertificateId.new(@ca_cert, @ca_cert, OpenSSL::Digest::SHA1.new)
+    bres.add_status(cid1, OpenSSL::OCSP::V_CERTSTATUS_REVOKED, OpenSSL::OCSP::REVOKED_STATUS_UNSPECIFIED, now - 400, -300, nil, nil)
+    bres.add_status(cid2, OpenSSL::OCSP::V_CERTSTATUS_GOOD, nil, nil, -300, 500, [])
+
+    assert_equal 2, bres.responses.size
+    single = bres.responses.first
+    assert_equal cid1.to_der, single.certid.to_der
+    assert_equal OpenSSL::OCSP::V_CERTSTATUS_REVOKED, single.cert_status
+    assert_equal OpenSSL::OCSP::REVOKED_STATUS_UNSPECIFIED, single.revocation_reason
+    assert_equal now - 400, single.revocation_time
+    assert_in_delta (now - 301), single.this_update, 1
+    assert_equal nil, single.next_update
+    assert_equal [], single.extensions
+
+    assert_equal cid2.to_der, bres.find_response(cid2).certid.to_der
+    assert_equal nil, bres.find_response(cid3)
+  end
+
+  def test_single_response_der
+    bres = OpenSSL::OCSP::BasicResponse.new
+    cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert)
+    bres.add_status(cid, OpenSSL::OCSP::V_CERTSTATUS_GOOD, nil, nil, -300, 500, nil)
+    single = bres.responses[0]
+    der = single.to_der
+    asn1 = OpenSSL::ASN1.decode(der)
+    assert_equal :CONTEXT_SPECIFIC, asn1.value[1].tag_class
+    assert_equal 0, asn1.value[1].tag # good
+    assert_equal der, OpenSSL::OCSP::SingleResponse.new(der).to_der
+  end
+
+  def test_single_response_check_validity
+    bres = OpenSSL::OCSP::BasicResponse.new
+    cid1 = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert, OpenSSL::Digest::SHA1.new)
+    cid2 = OpenSSL::OCSP::CertificateId.new(@ocsp_cert, @ca_cert, OpenSSL::Digest::SHA1.new)
+    bres.add_status(cid1, OpenSSL::OCSP::V_CERTSTATUS_REVOKED, OpenSSL::OCSP::REVOKED_STATUS_UNSPECIFIED, -400, -300, -50, [])
+    bres.add_status(cid2, OpenSSL::OCSP::V_CERTSTATUS_REVOKED, OpenSSL::OCSP::REVOKED_STATUS_UNSPECIFIED, -400, -300, nil, [])
+    bres.add_status(cid2, OpenSSL::OCSP::V_CERTSTATUS_GOOD, nil, nil, Time.now + 100, nil, nil)
+
+    if bres.responses[2].check_validity # thisUpdate is in future; must fail
+      # LibreSSL bug; skip for now
+      pend "OCSP_check_validity() is broken"
+    end
+
+    single1 = bres.responses[0]
+    assert_equal false, single1.check_validity
+    assert_equal false, single1.check_validity(30)
+    assert_equal true, single1.check_validity(60)
+    single2 = bres.responses[1]
+    assert_equal true, single2.check_validity
+    assert_equal true, single2.check_validity(0, 500)
+    assert_equal false, single2.check_validity(0, 200)
+  end
+
+  def test_response
+    bres = OpenSSL::OCSP::BasicResponse.new
+    cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert, OpenSSL::Digest::SHA1.new)
+    bres.add_status(cid, OpenSSL::OCSP::V_CERTSTATUS_GOOD, 0, nil, -300, 500, [])
+    bres.sign(@ocsp_cert, @ocsp_key, [])
+    res = OpenSSL::OCSP::Response.create(OpenSSL::OCSP::RESPONSE_STATUS_SUCCESSFUL, bres)
+
+    assert_equal bres.to_der, res.basic.to_der
+    assert_equal OpenSSL::OCSP::RESPONSE_STATUS_SUCCESSFUL, res.status
+  end
+
+  def test_response_der
+    bres = OpenSSL::OCSP::BasicResponse.new
+    cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert, OpenSSL::Digest::SHA1.new)
+    bres.add_status(cid, OpenSSL::OCSP::V_CERTSTATUS_GOOD, 0, nil, -300, 500, [])
+    bres.sign(@ocsp_cert, @ocsp_key, [@ca_cert], 0)
+    res = OpenSSL::OCSP::Response.create(OpenSSL::OCSP::RESPONSE_STATUS_SUCCESSFUL, bres)
+    der = res.to_der
+    asn1 = OpenSSL::ASN1.decode(der)
+    assert_equal OpenSSL::OCSP::RESPONSE_STATUS_SUCCESSFUL, asn1.value[0].value
+    assert_equal OpenSSL::ASN1.ObjectId("basicOCSPResponse").to_der, asn1.value[1].value[0].value[0].to_der
+    assert_equal bres.to_der, asn1.value[1].value[0].value[1].value
+    assert_equal der, OpenSSL::OCSP::Response.new(der).to_der
+  end
+
+  def test_response_dup
+    bres = OpenSSL::OCSP::BasicResponse.new
+    bres.sign(@ocsp_cert, @ocsp_key, [@ca_cert], 0)
+    res = OpenSSL::OCSP::Response.create(OpenSSL::OCSP::RESPONSE_STATUS_SUCCESSFUL, bres)
+    assert_equal res.to_der, res.dup.to_der
+  end
+end
+
+end
diff --git a/test/test_pair.rb b/test/test_pair.rb
new file mode 100644
index 0000000..9a5205f
--- /dev/null
+++ b/test/test_pair.rb
@@ -0,0 +1,493 @@
+# frozen_string_literal: false
+require_relative 'utils'
+
+if defined?(OpenSSL::TestUtils)
+
+require 'socket'
+require_relative 'ut_eof'
+
+module OpenSSL::SSLPairM
+  def server
+    host = "127.0.0.1"
+    port = 0
+    ctx = OpenSSL::SSL::SSLContext.new()
+    ctx.ciphers = "ADH"
+    ctx.security_level = 0
+    ctx.tmp_dh_callback = proc { OpenSSL::TestUtils::TEST_KEY_DH1024 }
+    tcps = create_tcp_server(host, port)
+    ssls = OpenSSL::SSL::SSLServer.new(tcps, ctx)
+    return ssls
+  end
+
+  def client(port)
+    host = "127.0.0.1"
+    ctx = OpenSSL::SSL::SSLContext.new()
+    ctx.ciphers = "ADH"
+    ctx.security_level = 0
+    s = create_tcp_client(host, port)
+    ssl = OpenSSL::SSL::SSLSocket.new(s, ctx)
+    ssl.connect
+    ssl.sync_close = true
+    ssl
+  end
+
+  def ssl_pair
+    ssls = server
+    th = Thread.new {
+      ns = ssls.accept
+      ssls.close
+      ns
+    }
+    port = ssls.to_io.local_address.ip_port
+    c = client(port)
+    s = th.value
+    if block_given?
+      begin
+        yield c, s
+      ensure
+        c.close unless c.closed?
+        s.close unless s.closed?
+      end
+    else
+      return c, s
+    end
+  ensure
+    if th&.alive?
+      th.kill
+      th.join
+    end
+  end
+end
+
+module OpenSSL::SSLPair
+  include OpenSSL::SSLPairM
+
+  def create_tcp_server(host, port)
+    TCPServer.new(host, port)
+  end
+
+  def create_tcp_client(host, port)
+    TCPSocket.new(host, port)
+  end
+end
+
+module OpenSSL::SSLPairLowlevelSocket
+  include OpenSSL::SSLPairM
+
+  def create_tcp_server(host, port)
+    Addrinfo.tcp(host, port).listen
+  end
+
+  def create_tcp_client(host, port)
+    Addrinfo.tcp(host, port).connect
+  end
+end
+
+module OpenSSL::TestEOF1M
+  def open_file(content)
+    s1, s2 = ssl_pair
+    th = Thread.new { s2 << content; s2.close }
+    yield s1
+  ensure
+    th.join if th
+    s1.close
+  end
+end
+
+module OpenSSL::TestEOF2M
+  def open_file(content)
+    s1, s2 = ssl_pair
+    th = Thread.new { s1 << content; s1.close }
+    yield s2
+  ensure
+    th.join if th
+    s2.close
+  end
+end
+
+module OpenSSL::TestPairM
+  def test_getc
+    ssl_pair {|s1, s2|
+      s1 << "a"
+      assert_equal(?a, s2.getc)
+    }
+  end
+
+  def test_gets
+    ssl_pair {|s1, s2|
+      s1 << "abc\n\n$def123ghi"
+      s1.close
+      ret = s2.gets
+      assert_equal Encoding::BINARY, ret.encoding
+      assert_equal "abc\n", ret
+      assert_equal "\n$", s2.gets("$")
+      assert_equal "def123", s2.gets(/\d+/)
+      assert_equal "ghi", s2.gets
+      assert_equal nil, s2.gets
+    }
+  end
+
+  def test_gets_eof_limit
+    ssl_pair {|s1, s2|
+      s1.write("hello")
+      s1.close # trigger EOF
+      assert_match "hello", s2.gets("\n", 6), "[ruby-core:70149] [Bug #11400]"
+    }
+  end
+
+  def test_readpartial
+    ssl_pair {|s1, s2|
+      s2.write "a\nbcd"
+      assert_equal("a\n", s1.gets)
+      result = ""
+      result << s1.readpartial(10) until result.length == 3
+      assert_equal("bcd", result)
+      s2.write "efg"
+      result = ""
+      result << s1.readpartial(10) until result.length == 3
+      assert_equal("efg", result)
+      s2.close
+      assert_raise(EOFError) { s1.readpartial(10) }
+      assert_raise(EOFError) { s1.readpartial(10) }
+      assert_equal("", s1.readpartial(0))
+    }
+  end
+
+  def test_readall
+    ssl_pair {|s1, s2|
+      s2.close
+      assert_equal("", s1.read)
+    }
+  end
+
+  def test_readline
+    ssl_pair {|s1, s2|
+      s2.close
+      assert_raise(EOFError) { s1.readline }
+    }
+  end
+
+  def test_puts_meta
+    ssl_pair {|s1, s2|
+      begin
+        old = $/
+        $/ = '*'
+        s1.puts 'a'
+      ensure
+        $/ = old
+      end
+      s1.close
+      assert_equal("a\n", s2.read)
+    }
+  end
+
+  def test_puts_empty
+    ssl_pair {|s1, s2|
+      s1.puts
+      s1.close
+      assert_equal("\n", s2.read)
+    }
+  end
+
+  def test_read_nonblock
+    ssl_pair {|s1, s2|
+      err = nil
+      assert_raise(OpenSSL::SSL::SSLErrorWaitReadable) {
+        begin
+          s2.read_nonblock(10)
+        ensure
+          err = $!
+        end
+      }
+      assert_kind_of(IO::WaitReadable, err)
+      s1.write "abc\ndef\n"
+      IO.select([s2])
+      assert_equal("ab", s2.read_nonblock(2))
+      assert_equal("c\n", s2.gets)
+      ret = nil
+      assert_nothing_raised("[ruby-core:20298]") { ret = s2.read_nonblock(10) }
+      assert_equal("def\n", ret)
+      s1.close
+      sleep 0.1
+      assert_raise(EOFError) { s2.read_nonblock(10) }
+    }
+  end
+
+  def test_read_nonblock_no_exception
+    ssl_pair {|s1, s2|
+      assert_equal :wait_readable, s2.read_nonblock(10, exception: false)
+      s1.write "abc\ndef\n"
+      IO.select([s2])
+      assert_equal("ab", s2.read_nonblock(2, exception: false))
+      assert_equal("c\n", s2.gets)
+      ret = nil
+      assert_nothing_raised("[ruby-core:20298]") { ret = s2.read_nonblock(10, exception: false) }
+      assert_equal("def\n", ret)
+      s1.close
+      sleep 0.1
+      assert_equal(nil, s2.read_nonblock(10, exception: false))
+    }
+  end
+
+  def write_nonblock(socket, meth, str)
+    ret = socket.send(meth, str)
+    ret.is_a?(Symbol) ? 0 : ret
+  end
+
+  def write_nonblock_no_ex(socket, str)
+    ret = socket.write_nonblock str, exception: false
+    ret.is_a?(Symbol) ? 0 : ret
+  end
+
+  def test_write_nonblock
+    ssl_pair {|s1, s2|
+      n = 0
+      begin
+        n += write_nonblock s1, :write_nonblock, "a" * 100000
+        n += write_nonblock s1, :write_nonblock, "b" * 100000
+        n += write_nonblock s1, :write_nonblock, "c" * 100000
+        n += write_nonblock s1, :write_nonblock, "d" * 100000
+        n += write_nonblock s1, :write_nonblock, "e" * 100000
+        n += write_nonblock s1, :write_nonblock, "f" * 100000
+      rescue IO::WaitWritable
+      end
+      s1.close
+      assert_equal(n, s2.read.length)
+    }
+  end
+
+  def test_write_nonblock_no_exceptions
+    ssl_pair {|s1, s2|
+      n = 0
+      n += write_nonblock_no_ex s1, "a" * 100000
+      n += write_nonblock_no_ex s1, "b" * 100000
+      n += write_nonblock_no_ex s1, "c" * 100000
+      n += write_nonblock_no_ex s1, "d" * 100000
+      n += write_nonblock_no_ex s1, "e" * 100000
+      n += write_nonblock_no_ex s1, "f" * 100000
+      s1.close
+      assert_equal(n, s2.read.length)
+    }
+  end
+
+  def test_write_nonblock_with_buffered_data
+    ssl_pair {|s1, s2|
+      s1.write "foo"
+      s1.write_nonblock("bar")
+      s1.write "baz"
+      s1.close
+      assert_equal("foobarbaz", s2.read)
+    }
+  end
+
+  def test_write_nonblock_with_buffered_data_no_exceptions
+    ssl_pair {|s1, s2|
+      s1.write "foo"
+      s1.write_nonblock("bar", exception: false)
+      s1.write "baz"
+      s1.close
+      assert_equal("foobarbaz", s2.read)
+    }
+  end
+
+  def test_write_nonblock_retry
+    ssl_pair {|s1, s2|
+      # fill up a socket so we hit EAGAIN
+      written = String.new
+      n = 0
+      buf = 'a' * 4099
+      case ret = s1.write_nonblock(buf, exception: false)
+      when :wait_readable then break
+      when :wait_writable then break
+      when Integer
+        written << buf
+        n += ret
+        exp = buf.bytesize
+        if ret != exp
+          buf = buf.byteslice(ret, exp - ret)
+        end
+      end while true
+      assert_kind_of Symbol, ret
+
+      # make more space for subsequent write:
+      readed = s2.read(n)
+      assert_equal written, readed
+
+      # this fails if SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER is missing:
+      buf2 = Marshal.load(Marshal.dump(buf))
+      assert_kind_of Integer, s1.write_nonblock(buf2, exception: false)
+    }
+  end
+
+  def test_write_zero
+    ssl_pair {|s1, s2|
+      assert_equal 0, s2.write_nonblock('', exception: false)
+      assert_kind_of Symbol, s1.read_nonblock(1, exception: false)
+      assert_equal 0, s2.syswrite('')
+      assert_kind_of Symbol, s1.read_nonblock(1, exception: false)
+      assert_equal 0, s2.write('')
+      assert_kind_of Symbol, s1.read_nonblock(1, exception: false)
+    }
+  end
+
+  def test_partial_tls_record_read_nonblock
+    ssl_pair { |s1, s2|
+      # the beginning of a TLS record
+      s1.io.write("\x17")
+      # should raise a IO::WaitReadable since a full TLS record is not available
+      # for reading
+      assert_raise(IO::WaitReadable) { s2.read_nonblock(1) }
+    }
+  end
+
+  def tcp_pair
+    host = "127.0.0.1"
+    serv = TCPServer.new(host, 0)
+    port = serv.connect_address.ip_port
+    sock1 = TCPSocket.new(host, port)
+    sock2 = serv.accept
+    serv.close
+    [sock1, sock2]
+  ensure
+    serv.close if serv && !serv.closed?
+  end
+
+  def test_connect_accept_nonblock_no_exception
+    ctx2 = OpenSSL::SSL::SSLContext.new
+    ctx2.ciphers = "ADH"
+    ctx2.security_level = 0
+    ctx2.tmp_dh_callback = proc { OpenSSL::TestUtils::TEST_KEY_DH1024 }
+
+    sock1, sock2 = tcp_pair
+
+    s2 = OpenSSL::SSL::SSLSocket.new(sock2, ctx2)
+    accepted = s2.accept_nonblock(exception: false)
+    assert_equal :wait_readable, accepted
+
+    ctx1 = OpenSSL::SSL::SSLContext.new
+    ctx1.ciphers = "ADH"
+    ctx1.security_level = 0
+    s1 = OpenSSL::SSL::SSLSocket.new(sock1, ctx1)
+    th = Thread.new do
+      rets = []
+      begin
+        rv = s1.connect_nonblock(exception: false)
+        rets << rv
+        case rv
+        when :wait_writable
+          IO.select(nil, [s1], nil, 5)
+        when :wait_readable
+          IO.select([s1], nil, nil, 5)
+        end
+      end until rv == s1
+      rets
+    end
+
+    until th.join(0.01)
+      accepted = s2.accept_nonblock(exception: false)
+      assert_include([s2, :wait_readable, :wait_writable ], accepted)
+    end
+
+    rets = th.value
+    assert_instance_of Array, rets
+    rets.each do |rv|
+      assert_include([s1, :wait_readable, :wait_writable ], rv)
+    end
+  ensure
+    th.join if th
+    s1.close if s1
+    s2.close if s2
+    sock1.close if sock1
+    sock2.close if sock2
+    accepted.close if accepted.respond_to?(:close)
+  end
+
+  def test_connect_accept_nonblock
+    ctx = OpenSSL::SSL::SSLContext.new()
+    ctx.ciphers = "ADH"
+    ctx.security_level = 0
+    ctx.tmp_dh_callback = proc { OpenSSL::TestUtils::TEST_KEY_DH1024 }
+
+    sock1, sock2 = tcp_pair
+
+    th = Thread.new {
+      s2 = OpenSSL::SSL::SSLSocket.new(sock2, ctx)
+      s2.sync_close = true
+      begin
+        sleep 0.2
+        s2.accept_nonblock
+      rescue IO::WaitReadable
+        IO.select([s2])
+        retry
+      rescue IO::WaitWritable
+        IO.select(nil, [s2])
+        retry
+      end
+      s2
+    }
+
+    sleep 0.1
+    ctx = OpenSSL::SSL::SSLContext.new()
+    ctx.ciphers = "ADH"
+    ctx.security_level = 0
+    s1 = OpenSSL::SSL::SSLSocket.new(sock1, ctx)
+    begin
+      sleep 0.2
+      s1.connect_nonblock
+    rescue IO::WaitReadable
+      IO.select([s1])
+      retry
+    rescue IO::WaitWritable
+      IO.select(nil, [s1])
+      retry
+    end
+    s1.sync_close = true
+
+    s2 = th.value
+
+    s1.print "a\ndef"
+    assert_equal("a\n", s2.gets)
+  ensure
+    th.join if th
+    s1.close if s1 && !s1.closed?
+    s2.close if s2 && !s2.closed?
+    sock1.close if sock1 && !sock1.closed?
+    sock2.close if sock2 && !sock2.closed?
+  end
+end
+
+class OpenSSL::TestEOF1 < OpenSSL::TestCase
+  include OpenSSL::TestEOF
+  include OpenSSL::SSLPair
+  include OpenSSL::TestEOF1M
+end
+
+class OpenSSL::TestEOF1LowlevelSocket < OpenSSL::TestCase
+  include OpenSSL::TestEOF
+  include OpenSSL::SSLPairLowlevelSocket
+  include OpenSSL::TestEOF1M
+end
+
+class OpenSSL::TestEOF2 < OpenSSL::TestCase
+  include OpenSSL::TestEOF
+  include OpenSSL::SSLPair
+  include OpenSSL::TestEOF2M
+end
+
+class OpenSSL::TestEOF2LowlevelSocket < OpenSSL::TestCase
+  include OpenSSL::TestEOF
+  include OpenSSL::SSLPairLowlevelSocket
+  include OpenSSL::TestEOF2M
+end
+
+class OpenSSL::TestPair < OpenSSL::TestCase
+  include OpenSSL::SSLPair
+  include OpenSSL::TestPairM
+end
+
+class OpenSSL::TestPairLowlevelSocket < OpenSSL::TestCase
+  include OpenSSL::SSLPairLowlevelSocket
+  include OpenSSL::TestPairM
+end
+
+end
diff --git a/test/test_pkcs12.rb b/test/test_pkcs12.rb
new file mode 100644
index 0000000..403718b
--- /dev/null
+++ b/test/test_pkcs12.rb
@@ -0,0 +1,315 @@
+# frozen_string_literal: false
+require_relative "utils"
+
+if defined?(OpenSSL::TestUtils)
+
+module OpenSSL
+  class TestPKCS12 < OpenSSL::TestCase
+    include OpenSSL::TestUtils
+
+    def setup
+      super
+      ca = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=CA")
+      ca_exts = [
+        ["basicConstraints","CA:TRUE",true],
+        ["keyUsage","keyCertSign, cRLSign",true],
+        ["subjectKeyIdentifier","hash",false],
+        ["authorityKeyIdentifier","keyid:always",false],
+      ]
+      @cacert = issue_cert(ca, TEST_KEY_RSA2048, 1, ca_exts, nil, nil)
+
+      inter_ca = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=Intermediate CA")
+      inter_ca_key = OpenSSL::PKey.read <<-_EOS_
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQDp7hIG0SFMG/VWv1dBUWziAPrNmkMXJgTCAoB7jffzRtyyN04K
+oq/89HAszTMStZoMigQURfokzKsjpUp8OYCAEsBtt9d5zPndWMz/gHN73GrXk3LT
+ZsxEn7Xv5Da+Y9F/Hx2QZUHarV5cdZixq2NbzWGwrToogOQMh2pxN3Z/0wIDAQAB
+AoGBAJysUyx3olpsGzv3OMRJeahASbmsSKTXVLZvoIefxOINosBFpCIhZccAG6UV
+5c/xCvS89xBw8aD15uUfziw3AuT8QPEtHCgfSjeT7aWzBfYswEgOW4XPuWr7EeI9
+iNHGD6z+hCN/IQr7FiEBgTp6A+i/hffcSdR83fHWKyb4M7TRAkEA+y4BNd668HmC
+G5MPRx25n6LixuBxrNp1umfjEI6UZgEFVpYOg4agNuimN6NqM253kcTR94QNTUs5
+Kj3EhG1YWwJBAO5rUjiOyCNVX2WUQrOMYK/c1lU7fvrkdygXkvIGkhsPoNRzLPeA
+HGJszKtrKD8bNihWpWNIyqKRHfKVD7yXT+kCQGCAhVCIGTRoypcDghwljHqLnysf
+ci0h5ZdPcIqc7ODfxYhFsJ/Rql5ONgYsT5Ig/+lOQAkjf+TRYM4c2xKx2/8CQBvG
+jv6dy70qDgIUgqzONtlmHeYyFzn9cdBO5sShdVYHvRHjFSMEXsosqK9zvW2UqvuK
+FJx7d3f29gkzynCLJDkCQGQZlEZJC4vWmWJGRKJ24P6MyQn3VsPfErSKOg4lvyM3
+Li8JsX5yIiuVYaBg/6ha3tOg4TCa5K/3r3tVliRZ2Es=
+-----END RSA PRIVATE KEY-----
+      _EOS_
+      @inter_cacert = issue_cert(inter_ca, inter_ca_key, 2, ca_exts, @cacert, TEST_KEY_RSA2048)
+
+      exts = [
+        ["keyUsage","digitalSignature",true],
+        ["subjectKeyIdentifier","hash",false],
+      ]
+      ee = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=Ruby PKCS12 Test Certificate")
+      @mycert = issue_cert(ee, TEST_KEY_RSA1024, 3, exts, @inter_cacert, inter_ca_key)
+    end
+
+    def test_create
+      pkcs12 = OpenSSL::PKCS12.create(
+        "omg",
+        "hello",
+        TEST_KEY_RSA1024,
+        @mycert
+      )
+      assert_equal @mycert, pkcs12.certificate
+      assert_equal TEST_KEY_RSA1024, pkcs12.key
+      assert_nil pkcs12.ca_certs
+    end
+
+    def test_create_no_pass
+      pkcs12 = OpenSSL::PKCS12.create(
+        nil,
+        "hello",
+        TEST_KEY_RSA1024,
+        @mycert
+      )
+      assert_equal @mycert, pkcs12.certificate
+      assert_equal TEST_KEY_RSA1024, pkcs12.key
+      assert_nil pkcs12.ca_certs
+
+      decoded = OpenSSL::PKCS12.new(pkcs12.to_der)
+      assert_cert @mycert, decoded.certificate
+    end
+
+    def test_create_with_chain
+      chain = [@inter_cacert, @cacert]
+
+      pkcs12 = OpenSSL::PKCS12.create(
+        "omg",
+        "hello",
+        TEST_KEY_RSA1024,
+        @mycert,
+        chain
+      )
+      assert_equal chain, pkcs12.ca_certs
+    end
+
+    def test_create_with_chain_decode
+      chain = [@cacert, @inter_cacert]
+
+      passwd = "omg"
+
+      pkcs12 = OpenSSL::PKCS12.create(
+        passwd,
+        "hello",
+        TEST_KEY_RSA1024,
+        @mycert,
+        chain
+      )
+
+      decoded = OpenSSL::PKCS12.new(pkcs12.to_der, passwd)
+      assert_equal chain.size, decoded.ca_certs.size
+      assert_include_cert @cacert, decoded.ca_certs
+      assert_include_cert @inter_cacert, decoded.ca_certs
+      assert_cert @mycert, decoded.certificate
+      assert_equal TEST_KEY_RSA1024.to_der, decoded.key.to_der
+    end
+
+    def test_create_with_bad_nid
+      assert_raise(ArgumentError) do
+        OpenSSL::PKCS12.create(
+          "omg",
+          "hello",
+          TEST_KEY_RSA1024,
+          @mycert,
+          [],
+          "foo"
+        )
+      end
+    end
+
+    def test_create_with_itr
+      OpenSSL::PKCS12.create(
+        "omg",
+        "hello",
+        TEST_KEY_RSA1024,
+        @mycert,
+        [],
+        nil,
+        nil,
+        2048
+      )
+
+      assert_raise(TypeError) do
+        OpenSSL::PKCS12.create(
+          "omg",
+          "hello",
+          TEST_KEY_RSA1024,
+          @mycert,
+          [],
+          nil,
+          nil,
+          "omg"
+        )
+      end
+    end
+
+    def test_create_with_mac_itr
+      OpenSSL::PKCS12.create(
+        "omg",
+        "hello",
+        TEST_KEY_RSA1024,
+        @mycert,
+        [],
+        nil,
+        nil,
+        nil,
+        2048
+      )
+
+      assert_raise(TypeError) do
+        OpenSSL::PKCS12.create(
+          "omg",
+          "hello",
+          TEST_KEY_RSA1024,
+          @mycert,
+          [],
+          nil,
+          nil,
+          nil,
+          "omg"
+        )
+      end
+    end
+
+    def test_new_with_one_key_and_one_cert
+      # generated with:
+      #   openssl version #=> OpenSSL 1.0.2h  3 May 2016
+      #   openssl pkcs12 -in <@mycert> -inkey <RSA1024> -export -out <out>
+      str = <<~EOF.unpack("m").first
+MIIGQQIBAzCCBgcGCSqGSIb3DQEHAaCCBfgEggX0MIIF8DCCAu8GCSqGSIb3DQEH
+BqCCAuAwggLcAgEAMIIC1QYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIeZPM
+Rh6KiXgCAggAgIICqL6O+LCZmBzdIg6mozPF3FpY0hVbWHvTNMiDHieW3CrAanhN
+YCH2/wHqH8WpFpEWwF0qEEXAWjHsIlYB4Cfqo6b7XpuZe5eVESsjNTOTMF1JCUJj
+A6iNefXmCFLync1JK5LUodRDhTlKLU1WPK20X9X4vuEwHn8wt5RUb8P0E+Xh6rpS
+XC4LkZKT45zF3cJa/n5+dW65ohVGNVnF9D1bCNEKHMOllK1V9omutQ9slW88hpga
+LGiFsJoFOb/ESGb78KO+bd6zbX1MdKdBV+WD6t1uF/cgU65y+2A4nXs1urda+MJ7
+7iVqiB7Vnc9cANTbAkTSGNyoUDVM/NZde782/8IvddLAzUZ2EftoRDke6PvuBOVL
+ljBhNWmdamrtBqzuzVZCRdWq44KZkF2Xoc9asepwIkdVmntzQF7f1Z+Ta5yg6HFp
+xnr7CuM+MlHEShXkMgYtHnwAq10fDMSXIvjhi/AA5XUAusDO3D+hbtcRDcJ4uUes
+dm5dhQE2qJ02Ysn4aH3o1F3RYNOzrxejHJwl0D2TCE8Ww2X342xib57+z9u03ufj
+jswhiMKxy67f1LhUMq3XrT3uV6kCVXk/KUOUPcXPlPVNA5JmZeFhMp6GrtB5xJJ9
+wwBZD8UL5A2U2Mxi2OZsdUBv8eo3jnjZ284aFpt+mCjIHrLW5O0jwY8OCwSlYUoY
+IY00wlabX0s82kBcIQNZbC1RSV2267ro/7A0MClc8YQ/zWN0FKY6apgtUkHJI1cL
+1dc77mhnjETjwW94iLMDFy4zQfVu7IfCBqOBzygRNnqqUG66UhTs1xFnWM0mWXl/
+Zh9+AMpbRLIPaKCktIjl5juzzm+KEgkhD+707XRCFIGUYGP5bSHzGaz8PK9hj0u1
+E2SpZHUvYOcawmxtA7pmpSxl5uQjMIIC+QYJKoZIhvcNAQcBoIIC6gSCAuYwggLi
+MIIC3gYLKoZIhvcNAQwKAQKgggKmMIICojAcBgoqhkiG9w0BDAEDMA4ECKB338m8
+qSzHAgIIAASCAoACFhJeqA3xx+s1qIH6udNQYY5hAL6oz7SXoGwFhDiceSyJjmAD
+Dby9XWM0bPl1Gj5nqdsuI/lAM++fJeoETk+rxw8q6Ofk2zUaRRE39qgpwBwSk44o
+0SAFJ6bzHpc5CFh6sZmDaUX5Lm9GtjnGFmmsPTSJT5an5JuJ9WczGBEd0nSBQhJq
+xHbTGZiN8i3SXcIH531Sub+CBIFWy5lyCKgDYh/kgJFGQAaWUOjLI+7dCEESonXn
+F3Jh2uPbnDF9MGJyAFoNgWFhgSpi1cf6AUi87GY4Oyur88ddJ1o0D0Kz2uw8/bpG
+s3O4PYnIW5naZ8mozzbnYByEFk7PoTwM7VhoFBfYNtBoAI8+hBnPY/Y71YUojEXf
+SeX6QbtkIANfzS1XuFNKElShC3DPQIHpKzaatEsfxHfP+8VOav6zcn4mioao7NHA
+x7Dp6R1enFGoQOq4UNjBT8YjnkG5vW8zQHW2dAHLTJBq6x2Fzm/4Pjo/8vM1FiGl
+BQdW5vfDeJ/l6NgQm3xR9ka2E2HaDqIcj1zWbN8jy/bHPFJYuF/HH8MBV/ngMIXE
+vFEW/ToYv8eif0+EpUtzBsCKD4a7qYYYh87RmEVoQU96q6m+UbhpD2WztYfAPkfo
+OSL9j2QHhVczhL7OAgqNeM95pOsjA9YMe7exTeqK31LYnTX8oH8WJD1xGbRSJYgu
+SY6PQbumcJkc/TFPn0GeVUpiDdf83SeG50lo/i7UKQi2l1hi5Y51fQhnBnyMr68D
+llSZEvSWqfDxBJkBpeg6PIYvkTpEwKRJpVQoM3uYvdqVSSnW6rydqIb+snfOrlhd
+f+xCtq9xr+kHeTSqLIDRRAnMfgFRhY3IBlj6MSUwIwYJKoZIhvcNAQkVMRYEFBdb
+8XGWehZ6oPj56Pf/uId46M9AMDEwITAJBgUrDgMCGgUABBRvSCB04/f8f13pp2PF
+vyl2WuMdEwQIMWFFphPkIUICAggA
+      EOF
+      p12 = OpenSSL::PKCS12.new(str, "abc123")
+
+      assert_equal TEST_KEY_RSA1024.to_der, p12.key.to_der
+      assert_equal @mycert.subject.to_der, p12.certificate.subject.to_der
+      assert_equal [], Array(p12.ca_certs)
+    end
+
+    def test_new_with_no_keys
+      # generated with:
+      #   openssl pkcs12 -in <@mycert> -nokeys -export -out <out>
+      str = <<~EOF.unpack("m").first
+MIIDHAIBAzCCAuIGCSqGSIb3DQEHAaCCAtMEggLPMIICyzCCAscGCSqGSIb3DQEH
+BqCCArgwggK0AgEAMIICrQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIX4+W
+irqwH40CAggAgIICgOaCyo+5+6IOVoGCCL80c50bkkzAwqdXxvkKExJSdcJz2uMU
+0gRrKnZEjL5wrUsN8RwZu8DvgQTEhNEkKsUgM7AWainmN/EnwohIdHZAHpm6WD67
+I9kLGp0/DHrqZrV9P2dLfhXLUSQE8PI0tqZPZ8UEABhizkViw4eISTkrOUN7pGbN
+Qtx/oqgitXDuX2polbxYYDwt9vfHZhykHoKgew26SeJyZfeMs/WZ6olEI4cQUAFr
+mvYGuC1AxEGTo9ERmU8Pm16j9Hr9PFk50WYe+rnk9oX3wJogQ7XUWS5kYf7XRycd
+NDkNiwV/ts94bbuaGZp1YA6I48FXpIc8b5fX7t9tY0umGaWy0bARe1L7o0Y89EPe
+lMg25rOM7j3uPtFG8whbSfdETSy57UxzzTcJ6UwexeaK6wb2jqEmj5AOoPLWeaX0
+LyOAszR3v7OPAcjIDYZGdrbb3MZ2f2vo2pdQfu9698BrWhXuM7Odh73RLhJVreNI
+aezNOAtPyBlvGiBQBGTzRIYHSLL5Y5aVj2vWLAa7hjm5qTL5C5mFdDIo6TkEMr6I
+OsexNQofEGs19kr8nARXDlcbEimk2VsPj4efQC2CEXZNzURsKca82pa62MJ8WosB
+DTFd8X06zZZ4nED50vLopZvyW4fyW60lELwOyThAdG8UchoAaz2baqP0K4de44yM
+Y5/yPFDu4+GoimipJfbiYviRwbzkBxYW8+958ILh0RtagLbvIGxbpaym9PqGjOzx
+ShNXjLK2aAFZsEizQ8kd09quJHU/ogq2cUXdqqhmOqPnUWrJVi/VCoRB3Pv1/lE4
+mrUgr2YZ11rYvBw6g5XvNvFcSc53OKyV7SLn0dwwMTAhMAkGBSsOAwIaBQAEFEWP
+1WRQykaoD4uJCpTx/wv0SLLBBAiDKI26LJK7xgICCAA=
+      EOF
+      p12 = OpenSSL::PKCS12.new(str, "abc123")
+
+      assert_equal nil, p12.key
+      assert_equal nil, p12.certificate
+      assert_equal 1, p12.ca_certs.size
+      assert_equal @mycert.subject.to_der, p12.ca_certs[0].subject.to_der
+    end
+
+    def test_new_with_no_certs
+      # generated with:
+      #   openssl pkcs12 -inkey <RSA1024> -nocerts -export -out <out>
+      str = <<~EOF.unpack("m").first
+MIIDJwIBAzCCAu0GCSqGSIb3DQEHAaCCAt4EggLaMIIC1jCCAtIGCSqGSIb3DQEH
+AaCCAsMEggK/MIICuzCCArcGCyqGSIb3DQEMCgECoIICpjCCAqIwHAYKKoZIhvcN
+AQwBAzAOBAg6AaYnJs84SwICCAAEggKAQzZH+fWSpcQYD1J7PsGSune85A++fLCQ
+V7tacp2iv95GJkxwYmfTP176pJdgs00mceB9UJ/u9EX5nD0djdjjQjwo6sgKjY0q
+cpVhZw8CMxw7kBD2dhtui0zT8z5hy03LePxsjEKsGiSbeVeeGbSfw/I6AAYbv+Uh
+O/YPBGumeHj/D2WKnfsHJLQ9GAV3H6dv5VKYNxjciK7f/JEyZCuUQGIN64QFHDhJ
+7fzLqd/ul3FZzJZO6a+dwvcgux09SKVXDRSeFmRCEX4b486iWhJJVspCo9P2KNne
+ORrpybr3ZSwxyoICmjyo8gj0OSnEfdx9790Ej1takPqSA1wIdSdBLekbZqB0RBQg
+DEuPOsXNo3QFi8ji1vu0WBRJZZSNC2hr5NL6lNR+DKxG8yzDll2j4W4BBIp22mAE
+7QRX7kVxu17QJXQhOUac4Dd1qXmzebP8t6xkAxD9L7BWEN5OdiXWwSWGjVjMBneX
+nYObi/3UT/aVc5WHMHK2BhCI1bwH51E6yZh06d5m0TQpYGUTWDJdWGBSrp3A+8jN
+N2PMQkWBFrXP3smHoTEN4oZC4FWiPsIEyAkQsfKRhcV9lGKl2Xgq54ROTFLnwKoj
+Z3zJScnq9qmNzvVZSMmDLkjLyDq0pxRxGKBvgouKkWY7VFFIwwBIJM39iDJ5NbBY
+i1AQFTRsRSsZrNVPasCXrIq7bhMoJZb/YZOGBLNyJVqKUoYXhtwsajzSq54VlWft
+JxsPayEd4Vi6O9EU1ahnj6qFEZiKFzsicgK2J1Rb8cYagrp0XWjHW0SBn5GVUWCg
+GUokSFG/0JTdeYTo/sQuG4qNgJkOolRjpeI48Fciq5VUWLvVdKioXzAxMCEwCQYF
+Kw4DAhoFAAQUYAuwVtGD1TdgbFK4Yal2XBgwUR4ECEawsN3rNaa6AgIIAA==
+      EOF
+      p12 = OpenSSL::PKCS12.new(str, "abc123")
+
+      assert_equal TEST_KEY_RSA1024.to_der, p12.key.to_der
+      assert_equal nil, p12.certificate
+      assert_equal [], Array(p12.ca_certs)
+    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
+      [
+        :subject,
+        :issuer,
+        :serial,
+        :not_before,
+        :not_after,
+      ].each do |attribute|
+        assert_equal expected.send(attribute), actual.send(attribute)
+      end
+      assert_equal expected.to_der, actual.to_der
+    end
+
+    def assert_include_cert cert, ary
+      der = cert.to_der
+      ary.each do |candidate|
+        if candidate.to_der == der
+          return true
+        end
+      end
+      false
+    end
+
+  end
+end
+
+end
diff --git a/test/test_pkcs5.rb b/test/test_pkcs5.rb
new file mode 100644
index 0000000..ad8132c
--- /dev/null
+++ b/test/test_pkcs5.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: false
+require_relative 'utils'
+
+class OpenSSL::TestPKCS5 < OpenSSL::TestCase
+
+  def test_pbkdf2_hmac_sha1_rfc6070_c_1_len_20
+    p ="password"
+    s = "salt"
+    c = 1
+    dk_len = 20
+    raw = %w{ 0c 60 c8 0f 96 1f 0e 71
+              f3 a9 b5 24 af 60 12 06
+              2f e0 37 a6 }
+    expected = [raw.join('')].pack('H*')
+    value = OpenSSL::PKCS5.pbkdf2_hmac_sha1(p, s, c, dk_len)
+    assert_equal(expected, value)
+  end
+
+  def test_pbkdf2_hmac_sha1_rfc6070_c_2_len_20
+    p ="password"
+    s = "salt"
+    c = 2
+    dk_len = 20
+    raw = %w{ ea 6c 01 4d c7 2d 6f 8c
+              cd 1e d9 2a ce 1d 41 f0
+              d8 de 89 57 }
+    expected = [raw.join('')].pack('H*')
+    value = OpenSSL::PKCS5.pbkdf2_hmac_sha1(p, s, c, dk_len)
+    assert_equal(expected, value)
+  end
+
+  def test_pbkdf2_hmac_sha1_rfc6070_c_4096_len_20
+    p ="password"
+    s = "salt"
+    c = 4096
+    dk_len = 20
+    raw = %w{ 4b 00 79 01 b7 65 48 9a
+              be ad 49 d9 26 f7 21 d0
+              65 a4 29 c1 }
+    expected = [raw.join('')].pack('H*')
+    value = OpenSSL::PKCS5.pbkdf2_hmac_sha1(p, s, c, dk_len)
+    assert_equal(expected, value)
+  end
+
+# takes too long!
+#  def test_pbkdf2_hmac_sha1_rfc6070_c_16777216_len_20
+#    p ="password"
+#    s = "salt"
+#    c = 16777216
+#    dk_len = 20
+#    raw = %w{ ee fe 3d 61 cd 4d a4 e4
+#              e9 94 5b 3d 6b a2 15 8c
+#              26 34 e9 84 }
+#    expected = [raw.join('')].pack('H*')
+#    value = OpenSSL::PKCS5.pbkdf2_hmac_sha1(p, s, c, dk_len)
+#    assert_equal(expected, value)
+#  end
+
+  def test_pbkdf2_hmac_sha1_rfc6070_c_4096_len_25
+    p ="passwordPASSWORDpassword"
+    s = "saltSALTsaltSALTsaltSALTsaltSALTsalt"
+    c = 4096
+    dk_len = 25
+
+    raw = %w{ 3d 2e ec 4f e4 1c 84 9b
+              80 c8 d8 36 62 c0 e4 4a
+              8b 29 1a 96 4c f2 f0 70
+              38 }
+    expected = [raw.join('')].pack('H*')
+    value = OpenSSL::PKCS5.pbkdf2_hmac_sha1(p, s, c, dk_len)
+    assert_equal(expected, value)
+  end
+
+  def test_pbkdf2_hmac_sha1_rfc6070_c_4096_len_16
+    p ="pass\0word"
+    s = "sa\0lt"
+    c = 4096
+    dk_len = 16
+    raw = %w{ 56 fa 6a a7 55 48 09 9d
+              cc 37 d7 f0 34 25 e0 c3 }
+    expected = [raw.join('')].pack('H*')
+    value = OpenSSL::PKCS5.pbkdf2_hmac_sha1(p, s, c, dk_len)
+    assert_equal(expected, value)
+  end
+
+  def test_pbkdf2_hmac_sha256_c_20000_len_32
+    #unfortunately no official test vectors available yet for SHA-2
+    p ="password"
+    s = OpenSSL::Random.random_bytes(16)
+    c = 20000
+    dk_len = 32
+    digest = OpenSSL::Digest::SHA256.new
+    value1 = OpenSSL::PKCS5.pbkdf2_hmac(p, s, c, dk_len, digest)
+    value2 = OpenSSL::PKCS5.pbkdf2_hmac(p, s, c, dk_len, digest)
+    assert_equal(value1, value2)
+  end if OpenSSL::PKCS5.respond_to?(:pbkdf2_hmac)
+
+end if defined?(OpenSSL::TestUtils)
diff --git a/test/test_pkcs7.rb b/test/test_pkcs7.rb
new file mode 100644
index 0000000..3219155
--- /dev/null
+++ b/test/test_pkcs7.rb
@@ -0,0 +1,289 @@
+# frozen_string_literal: false
+require_relative 'utils'
+
+if defined?(OpenSSL::TestUtils)
+
+class OpenSSL::TestPKCS7 < OpenSSL::TestCase
+  def setup
+    super
+    @rsa1024 = OpenSSL::TestUtils::TEST_KEY_RSA1024
+    @rsa2048 = OpenSSL::TestUtils::TEST_KEY_RSA2048
+    ca = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=CA")
+    ee1 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=EE1")
+    ee2 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=EE2")
+
+    ca_exts = [
+      ["basicConstraints","CA:TRUE",true],
+      ["keyUsage","keyCertSign, cRLSign",true],
+      ["subjectKeyIdentifier","hash",false],
+      ["authorityKeyIdentifier","keyid:always",false],
+    ]
+    @ca_cert = issue_cert(ca, @rsa2048, 1, ca_exts, nil, nil)
+    ee_exts = [
+      ["keyUsage","Non Repudiation, Digital Signature, Key Encipherment",true],
+      ["authorityKeyIdentifier","keyid:always",false],
+      ["extendedKeyUsage","clientAuth, emailProtection, codeSigning",false],
+    ]
+    @ee1_cert = issue_cert(ee1, @rsa1024, 2, ee_exts, @ca_cert, @rsa2048)
+    @ee2_cert = issue_cert(ee2, @rsa1024, 3, ee_exts, @ca_cert, @rsa2048)
+  end
+
+  def issue_cert(*args)
+    OpenSSL::TestUtils.issue_cert(*args)
+  end
+
+  def test_signed
+    store = OpenSSL::X509::Store.new
+    store.add_cert(@ca_cert)
+    ca_certs = [@ca_cert]
+
+    data = "aaaaa\r\nbbbbb\r\nccccc\r\n"
+    tmp = OpenSSL::PKCS7.sign(@ee1_cert, @rsa1024, data, ca_certs)
+    p7 = OpenSSL::PKCS7.new(tmp.to_der)
+    certs = p7.certificates
+    signers = p7.signers
+    assert(p7.verify([], store))
+    assert_equal(data, p7.data)
+    assert_equal(2, certs.size)
+    assert_equal(@ee1_cert.subject.to_s, certs[0].subject.to_s)
+    assert_equal(@ca_cert.subject.to_s, certs[1].subject.to_s)
+    assert_equal(1, signers.size)
+    assert_equal(@ee1_cert.serial, signers[0].serial)
+    assert_equal(@ee1_cert.issuer.to_s, signers[0].issuer.to_s)
+
+    # Normally OpenSSL tries to translate the supplied content into canonical
+    # MIME format (e.g. a newline character is converted into CR+LF).
+    # If the content is a binary, PKCS7::BINARY flag should be used.
+
+    data = "aaaaa\nbbbbb\nccccc\n"
+    flag = OpenSSL::PKCS7::BINARY
+    tmp = OpenSSL::PKCS7.sign(@ee1_cert, @rsa1024, data, ca_certs, flag)
+    p7 = OpenSSL::PKCS7.new(tmp.to_der)
+    certs = p7.certificates
+    signers = p7.signers
+    assert(p7.verify([], store))
+    assert_equal(data, p7.data)
+    assert_equal(2, certs.size)
+    assert_equal(@ee1_cert.subject.to_s, certs[0].subject.to_s)
+    assert_equal(@ca_cert.subject.to_s, certs[1].subject.to_s)
+    assert_equal(1, signers.size)
+    assert_equal(@ee1_cert.serial, signers[0].serial)
+    assert_equal(@ee1_cert.issuer.to_s, signers[0].issuer.to_s)
+
+    # A signed-data which have multiple signatures can be created
+    # through the following steps.
+    #   1. create two signed-data
+    #   2. copy signerInfo and certificate from one to another
+
+    tmp1 = OpenSSL::PKCS7.sign(@ee1_cert, @rsa1024, data, [], flag)
+    tmp2 = OpenSSL::PKCS7.sign(@ee2_cert, @rsa1024, data, [], flag)
+    tmp1.add_signer(tmp2.signers[0])
+    tmp1.add_certificate(@ee2_cert)
+
+    p7 = OpenSSL::PKCS7.new(tmp1.to_der)
+    certs = p7.certificates
+    signers = p7.signers
+    assert(p7.verify([], store))
+    assert_equal(data, p7.data)
+    assert_equal(2, certs.size)
+    assert_equal(2, signers.size)
+    assert_equal(@ee1_cert.serial, signers[0].serial)
+    assert_equal(@ee1_cert.issuer.to_s, signers[0].issuer.to_s)
+    assert_equal(@ee2_cert.serial, signers[1].serial)
+    assert_equal(@ee2_cert.issuer.to_s, signers[1].issuer.to_s)
+  end
+
+  def test_detached_sign
+    store = OpenSSL::X509::Store.new
+    store.add_cert(@ca_cert)
+    ca_certs = [@ca_cert]
+
+    data = "aaaaa\nbbbbb\nccccc\n"
+    flag = OpenSSL::PKCS7::BINARY|OpenSSL::PKCS7::DETACHED
+    tmp = OpenSSL::PKCS7.sign(@ee1_cert, @rsa1024, data, ca_certs, flag)
+    p7 = OpenSSL::PKCS7.new(tmp.to_der)
+    assert_nothing_raised do
+      OpenSSL::ASN1.decode(p7)
+    end
+
+    certs = p7.certificates
+    signers = p7.signers
+    assert(!p7.verify([], store))
+    assert(p7.verify([], store, data))
+    assert_equal(data, p7.data)
+    assert_equal(2, certs.size)
+    assert_equal(@ee1_cert.subject.to_s, certs[0].subject.to_s)
+    assert_equal(@ca_cert.subject.to_s, certs[1].subject.to_s)
+    assert_equal(1, signers.size)
+    assert_equal(@ee1_cert.serial, signers[0].serial)
+    assert_equal(@ee1_cert.issuer.to_s, signers[0].issuer.to_s)
+  end
+
+  def test_enveloped
+    certs = [@ee1_cert, @ee2_cert]
+    cipher = OpenSSL::Cipher::AES.new("128-CBC")
+    data = "aaaaa\nbbbbb\nccccc\n"
+
+    tmp = OpenSSL::PKCS7.encrypt(certs, data, cipher, OpenSSL::PKCS7::BINARY)
+    p7 = OpenSSL::PKCS7.new(tmp.to_der)
+    recip = p7.recipients
+    assert_equal(:enveloped, p7.type)
+    assert_equal(2, recip.size)
+
+    assert_equal(@ca_cert.subject.to_s, recip[0].issuer.to_s)
+    assert_equal(2, recip[0].serial)
+    assert_equal(data, p7.decrypt(@rsa1024, @ee1_cert))
+
+    assert_equal(@ca_cert.subject.to_s, recip[1].issuer.to_s)
+    assert_equal(3, recip[1].serial)
+    assert_equal(data, p7.decrypt(@rsa1024, @ee2_cert))
+  end
+
+  def test_graceful_parsing_failure #[ruby-core:43250]
+    contents = File.read(__FILE__)
+    assert_raise(ArgumentError) { OpenSSL::PKCS7.new(contents) }
+  end
+
+  def test_set_type_signed
+    p7 = OpenSSL::PKCS7.new
+    p7.type = "signed"
+    assert_equal(:signed, p7.type)
+  end
+
+  def test_set_type_data
+    p7 = OpenSSL::PKCS7.new
+    p7.type = "data"
+    assert_equal(:data, p7.type)
+  end
+
+  def test_set_type_signed_and_enveloped
+    p7 = OpenSSL::PKCS7.new
+    p7.type = "signedAndEnveloped"
+    assert_equal(:signedAndEnveloped, p7.type)
+  end
+
+  def test_set_type_enveloped
+    p7 = OpenSSL::PKCS7.new
+    p7.type = "enveloped"
+    assert_equal(:enveloped, p7.type)
+  end
+
+  def test_set_type_encrypted
+    p7 = OpenSSL::PKCS7.new
+    p7.type = "encrypted"
+    assert_equal(:encrypted, p7.type)
+  end
+
+  def test_degenerate_pkcs7
+    ca_cert_pem = <<END
+-----BEGIN CERTIFICATE-----
+MIID4DCCAsigAwIBAgIJAL1oVI72wmQwMA0GCSqGSIb3DQEBBQUAMFMxCzAJBgNV
+BAYTAkFVMQ4wDAYDVQQIEwVTdGF0ZTENMAsGA1UEBxMEQ2l0eTEQMA4GA1UEChMH
+RXhhbXBsZTETMBEGA1UEAxMKRXhhbXBsZSBDQTAeFw0xMjEwMTgwOTE2NTBaFw0y
+MjEwMTYwOTE2NTBaMFMxCzAJBgNVBAYTAkFVMQ4wDAYDVQQIEwVTdGF0ZTENMAsG
+A1UEBxMEQ2l0eTEQMA4GA1UEChMHRXhhbXBsZTETMBEGA1UEAxMKRXhhbXBsZSBD
+QTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMTSPNxOkd5NN19XO0fJ
+tGVlWN4DWuvVL9WbWnXJXX9rU6X8sSOL9RrRA64eEZf2UBFjz9fMHZj/OGcxZpus
+4YtzfSrMU6xfvsIHeqX+mT60ms2RfX4UXab50MQArBin3JVKHGnOi25uyAOylVFU
+TuzzQJvKyB67vjuRPMlVAgVAZAP07ru9gW0ajt/ODxvUfvXxp5SFF68mVP2ipMBr
+4fujUwQC6cVHmnuL6p87VFoo9uk87TSQVDOQGL8MK4moMFtEW9oUTU22CgnxnCsS
+sCCELYhy9BdaTWQH26LzMfhnwSuIRHZyprW4WZtU0akrYXNiCj8o92rZmQWXJDbl
+qNECAwEAAaOBtjCBszAdBgNVHQ4EFgQUNtVw4jvkZZbkdQbkYi2/F4QN79owgYMG
+A1UdIwR8MHqAFDbVcOI75GWW5HUG5GItvxeEDe/aoVekVTBTMQswCQYDVQQGEwJB
+VTEOMAwGA1UECBMFU3RhdGUxDTALBgNVBAcTBENpdHkxEDAOBgNVBAoTB0V4YW1w
+bGUxEzARBgNVBAMTCkV4YW1wbGUgQ0GCCQC9aFSO9sJkMDAMBgNVHRMEBTADAQH/
+MA0GCSqGSIb3DQEBBQUAA4IBAQBvJIsY9bIqliZ3WD1KoN4cvAQeRAPsoLXQkkHg
+P6Nrcw9rJ5JvoHfYbo5aNlwbnkbt/B2xlVEXUYpJoBZFXafgxG2gJleioIgnaDS4
+FPPwZf1C5ZrOgUBfxTGjHex4ghSAoNGOd35jQzin5NGKOvZclPjZ2vQ++LP3aA2l
+9Fn2qASS46IzMGJlC75mlTOTQwDM16UunMAK26lNG9J6q02o4d/oU2a7x0fD80yF
+64kNA1wDAwaVCYiUH541qKp+b4iDqer8nf8HqzYDFlpje18xYZMEd1hj8dVOharM
+pISJ+D52hV/BGEYF8r5k3hpC5d76gSP2oCcaY0XvLBf97qik
+-----END CERTIFICATE-----
+END
+    p7 = OpenSSL::PKCS7.new
+    p7.type = "signed"
+    ca_cert = OpenSSL::X509::Certificate.new(ca_cert_pem)
+    p7.add_certificate ca_cert
+    p7.add_data ""
+
+    assert_nothing_raised do
+      p7.to_pem
+    end
+  end
+
+  def test_split_content
+     pki_message_pem = <<END
+-----BEGIN PKCS7-----
+MIIHSwYJKoZIhvcNAQcCoIIHPDCCBzgCAQExCzAJBgUrDgMCGgUAMIIDiAYJKoZI
+hvcNAQcBoIIDeQSCA3UwgAYJKoZIhvcNAQcDoIAwgAIBADGCARAwggEMAgEAMHUw
+cDEQMA4GA1UECgwHZXhhbXBsZTEXMBUGA1UEAwwOVEFSTUFDIFJPT1QgQ0ExIjAg
+BgkqhkiG9w0BCQEWE3NvbWVvbmVAZXhhbXBsZS5vcmcxCzAJBgNVBAYTAlVTMRIw
+EAYDVQQHDAlUb3duIEhhbGwCAWYwDQYJKoZIhvcNAQEBBQAEgYBspXXse8ZhG1FE
+E3PVAulbvrdR52FWPkpeLvSjgEkYzTiUi0CC3poUL1Ku5mOlavWAJgoJpFICDbvc
+N4ZNDCwOhnzoI9fMGmm1gvPQy15BdhhZRo9lP7Ga/Hg2APKT0/0yhPsmJ+w+u1e7
+OoJEVeEZ27x3+u745bGEcu8of5th6TCABgkqhkiG9w0BBwEwFAYIKoZIhvcNAwcE
+CBNs2U5mMsd/oIAEggIQU6cur8QBz02/4eMpHdlU9IkyrRMiaMZ/ky9zecOAjnvY
+d2jZqS7RhczpaNJaSli3GmDsKrF+XqE9J58s9ScGqUigzapusTsxIoRUPr7Ztb0a
+pg8VWDipAsuw7GfEkgx868sV93uC4v6Isfjbhd+JRTFp/wR1kTi7YgSXhES+RLUW
+gQbDIDgEQYxJ5U951AJtnSpjs9za2ZkTdd8RSEizJK0bQ1vqLoApwAVgZqluATqQ
+AHSDCxhweVYw6+y90B9xOrqPC0eU7Wzryq2+Raq5ND2Wlf5/N11RQ3EQdKq/l5Te
+ijp9PdWPlkUhWVoDlOFkysjk+BE+7AkzgYvz9UvBjmZsMsWqf+KsZ4S8/30ndLzu
+iucsu6eOnFLLX8DKZxV6nYffZOPzZZL8hFBcE7PPgSdBEkazMrEBXq1j5mN7exbJ
+NOA5uGWyJNBMOCe+1JbxG9UeoqvCCTHESxEeDu7xR3NnSOD47n7cXwHr81YzK2zQ
+5oWpP3C8jzI7tUjLd1S0Z3Psd17oaCn+JOfUtuB0nc3wfPF/WPo0xZQodWxp2/Cl
+EltR6qr1zf5C7GwmLzBZ6bHFAIT60/JzV0/56Pn8ztsRFtI4cwaBfTfvnwi8/sD9
+/LYOMY+/b6UDCUSR7RTN7XfrtAqDEzSdzdJkOWm1jvM8gkLmxpZdvxG3ZvDYnEQE
+5Nq+un5nAny1wf3rWierBAjE5ntiAmgs5AAAAAAAAAAAAACgggHqMIIB5jCCAU+g
+AwIBAgIBATANBgkqhkiG9w0BAQUFADAvMS0wKwYDVQQDEyQwQUM5RjAyNi1EQ0VB
+LTRDMTItOTEyNy1DMEZEN0QyQThCNUEwHhcNMTIxMDE5MDk0NTQ3WhcNMTMxMDE5
+MDk0NTQ3WjAvMS0wKwYDVQQDEyQwQUM5RjAyNi1EQ0VBLTRDMTItOTEyNy1DMEZE
+N0QyQThCNUEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALTsTNyGIsKvyw56
+WI3Gll/RmjsupkrdEtPbx7OjS9MEgyhOAf9+u6CV0LJGHpy7HUeROykF6xpbSdCm
+Mr6kNObl5N0ljOb8OmV4atKjmGg1rWawDLyDQ9Dtuby+dzfHtzAzP+J/3ZoOtSqq
+AHVTnCclU1pm/uHN0HZ5nL5iLJTvAgMBAAGjEjAQMA4GA1UdDwEB/wQEAwIFoDAN
+BgkqhkiG9w0BAQUFAAOBgQA8K+BouEV04HRTdMZd3akjTQOm6aEGW4nIRnYIf8ZV
+mvUpLirVlX/unKtJinhGisFGpuYLMpemx17cnGkBeLCQRvHQjC+ho7l8/LOGheMS
+nvu0XHhvmJtRbm8MKHhogwZqHFDnXonvjyqhnhEtK5F2Fimcce3MoF2QtEe0UWv/
+8DGCAaowggGmAgEBMDQwLzEtMCsGA1UEAxMkMEFDOUYwMjYtRENFQS00QzEyLTkx
+MjctQzBGRDdEMkE4QjVBAgEBMAkGBSsOAwIaBQCggc0wEgYKYIZIAYb4RQEJAjEE
+EwIxOTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0x
+MjEwMTkwOTQ1NDdaMCAGCmCGSAGG+EUBCQUxEgQQ2EFUJdQNwQDxclIQ8qNyYzAj
+BgkqhkiG9w0BCQQxFgQUy8GFXPpAwRJUT3rdvNC9Pn+4eoswOAYKYIZIAYb4RQEJ
+BzEqEygwRkU3QzJEQTVEMDc2NzFFOTcxNDlCNUE3MDRCMERDNkM4MDYwRDJBMA0G
+CSqGSIb3DQEBAQUABIGAWUNdzvU2iiQOtihBwF0h48Nnw/2qX8uRjg6CVTOMcGji
+BxjUMifEbT//KJwljshl4y3yBLqeVYLOd04k6aKSdjgdZnrnUPI6p5tL5PfJkTAE
+L6qflZ9YCU5erE4T5U98hCQBMh4nOYxgaTjnZzhpkKQuEiKq/755cjzTzlI/eok=
+-----END PKCS7-----
+END
+    pki_message_content_pem = <<END
+-----BEGIN PKCS7-----
+MIIDawYJKoZIhvcNAQcDoIIDXDCCA1gCAQAxggEQMIIBDAIBADB1MHAxEDAOBgNV
+BAoMB2V4YW1wbGUxFzAVBgNVBAMMDlRBUk1BQyBST09UIENBMSIwIAYJKoZIhvcN
+AQkBFhNzb21lb25lQGV4YW1wbGUub3JnMQswCQYDVQQGEwJVUzESMBAGA1UEBwwJ
+VG93biBIYWxsAgFmMA0GCSqGSIb3DQEBAQUABIGAbKV17HvGYRtRRBNz1QLpW763
+UedhVj5KXi70o4BJGM04lItAgt6aFC9SruZjpWr1gCYKCaRSAg273DeGTQwsDoZ8
+6CPXzBpptYLz0MteQXYYWUaPZT+xmvx4NgDyk9P9MoT7JifsPrtXuzqCRFXhGdu8
+d/ru+OWxhHLvKH+bYekwggI9BgkqhkiG9w0BBwEwFAYIKoZIhvcNAwcECBNs2U5m
+Msd/gIICGFOnLq/EAc9Nv+HjKR3ZVPSJMq0TImjGf5Mvc3nDgI572Hdo2aku0YXM
+6WjSWkpYtxpg7Cqxfl6hPSefLPUnBqlIoM2qbrE7MSKEVD6+2bW9GqYPFVg4qQLL
+sOxnxJIMfOvLFfd7guL+iLH424XfiUUxaf8EdZE4u2IEl4REvkS1FoEGwyA4BEGM
+SeVPedQCbZ0qY7Pc2tmZE3XfEUhIsyStG0Nb6i6AKcAFYGapbgE6kAB0gwsYcHlW
+MOvsvdAfcTq6jwtHlO1s68qtvkWquTQ9lpX+fzddUUNxEHSqv5eU3oo6fT3Vj5ZF
+IVlaA5ThZMrI5PgRPuwJM4GL8/VLwY5mbDLFqn/irGeEvP99J3S87ornLLunjpxS
+y1/AymcVep2H32Tj82WS/IRQXBOzz4EnQRJGszKxAV6tY+Zje3sWyTTgObhlsiTQ
+TDgnvtSW8RvVHqKrwgkxxEsRHg7u8UdzZ0jg+O5+3F8B6/NWMyts0OaFqT9wvI8y
+O7VIy3dUtGdz7Hde6Ggp/iTn1LbgdJ3N8Hzxf1j6NMWUKHVsadvwpRJbUeqq9c3+
+QuxsJi8wWemxxQCE+tPyc1dP+ej5/M7bERbSOHMGgX03758IvP7A/fy2DjGPv2+l
+AwlEke0Uze1367QKgxM0nc3SZDlptY7zPIJC5saWXb8Rt2bw2JxEBOTavrp+ZwJ8
+tcH961onq8Tme2ICaCzk
+-----END PKCS7-----
+END
+    pki_msg = OpenSSL::PKCS7.new(pki_message_pem)
+    store = OpenSSL::X509::Store.new
+    pki_msg.verify(nil, store, nil, OpenSSL::PKCS7::NOVERIFY)
+    p7enc = OpenSSL::PKCS7.new(pki_msg.data)
+    assert_equal(pki_message_content_pem, p7enc.to_pem)
+  end
+end
+
+end
diff --git a/test/test_pkey_dh.rb b/test/test_pkey_dh.rb
new file mode 100644
index 0000000..470c952
--- /dev/null
+++ b/test/test_pkey_dh.rb
@@ -0,0 +1,120 @@
+# frozen_string_literal: false
+require_relative 'utils'
+
+if defined?(OpenSSL::TestUtils)
+
+class OpenSSL::TestPKeyDH < OpenSSL::PKeyTestCase
+  DH1024 = OpenSSL::TestUtils::TEST_KEY_DH1024
+
+  NEW_KEYLEN = 256
+
+  def test_DEFAULT_parameters
+    list = {
+      1024 => OpenSSL::PKey::DH::DEFAULT_1024,
+      2048 => OpenSSL::PKey::DH::DEFAULT_2048,
+    }
+
+    list.each do |expected_size, dh|
+      assert_equal expected_size, dh.p.num_bits
+      assert_predicate dh.p, :prime?
+      result, remainder = (dh.p - 1) / 2
+      assert_predicate result, :prime?
+      assert_equal 0, remainder
+      assert_no_key dh
+    end
+  end
+
+  def test_new
+    dh = OpenSSL::PKey::DH.new(NEW_KEYLEN)
+    assert_key(dh)
+  end
+
+  def test_new_break
+    assert_nil(OpenSSL::PKey::DH.new(NEW_KEYLEN) { break })
+    assert_raise(RuntimeError) do
+      OpenSSL::PKey::DH.new(NEW_KEYLEN) { raise }
+    end
+  end
+
+  def test_DHparams
+    asn1 = OpenSSL::ASN1::Sequence([
+      OpenSSL::ASN1::Integer(DH1024.p),
+      OpenSSL::ASN1::Integer(DH1024.g)
+    ])
+    key = OpenSSL::PKey::DH.new(asn1.to_der)
+    assert_same_dh dup_public(DH1024), key
+
+    pem = <<~EOF
+    -----BEGIN DH PARAMETERS-----
+    MIGHAoGBAKnKQ8MNK6nYZzLrrcuTsLxuiJGXoOO5gT+tljOTbHBuiktdMTITzIY0
+    pFxIvjG05D7HoBZQfrR0c92NGWPkAiCkhQKB8JCbPVzwNLDy6DZ0pmofDKrEsYHG
+    AQjjxMXhwULlmuR/K+WwlaZPiLIBYalLAZQ7ZbOPeVkJ8ePao0eLAgEC
+    -----END DH PARAMETERS-----
+    EOF
+    key = OpenSSL::PKey::DH.new(pem)
+    assert_same_dh dup_public(DH1024), key
+
+    assert_equal asn1.to_der, DH1024.to_der
+    assert_equal pem, DH1024.export
+  end
+
+  def test_public_key
+    dh = OpenSSL::TestUtils::TEST_KEY_DH1024
+    public_key = dh.public_key
+    assert_no_key(public_key) #implies public_key.public? is false!
+    assert_equal(dh.to_der, public_key.to_der)
+    assert_equal(dh.to_pem, public_key.to_pem)
+  end
+
+  def test_generate_key
+    dh = OpenSSL::TestUtils::TEST_KEY_DH1024.public_key # creates a copy
+    assert_no_key(dh)
+    dh.generate_key!
+    assert_key(dh)
+  end
+
+  def test_key_exchange
+    dh = OpenSSL::TestUtils::TEST_KEY_DH1024
+    dh2 = dh.public_key
+    dh.generate_key!
+    dh2.generate_key!
+    assert_equal(dh.compute_key(dh2.pub_key), dh2.compute_key(dh.pub_key))
+  end
+
+  def test_dup
+    dh = OpenSSL::PKey::DH.new(NEW_KEYLEN)
+    dh2 = dh.dup
+    assert_equal dh.to_der, dh2.to_der # params
+    assert_equal_params dh, dh2 # keys
+    dh2.set_pqg(dh2.p + 1, nil, dh2.g)
+    assert_not_equal dh2.p, dh.p
+    assert_equal dh2.g, dh.g
+  end
+
+  private
+
+  def assert_equal_params(dh1, dh2)
+    assert_equal(dh1.g, dh2.g)
+    assert_equal(dh1.p, dh2.p)
+  end
+
+  def assert_no_key(dh)
+    assert_equal(false, dh.public?)
+    assert_equal(false, dh.private?)
+    assert_equal(nil, dh.pub_key)
+    assert_equal(nil, dh.priv_key)
+  end
+
+  def assert_key(dh)
+    assert(dh.public?)
+    assert(dh.private?)
+    assert(dh.pub_key)
+    assert(dh.priv_key)
+  end
+
+  def assert_same_dh(expected, key)
+    check_component(expected, key, [:p, :q, :g, :pub_key, :priv_key])
+  end
+end
+
+end
diff --git a/test/test_pkey_dsa.rb b/test/test_pkey_dsa.rb
new file mode 100644
index 0000000..a4ccd1d
--- /dev/null
+++ b/test/test_pkey_dsa.rb
@@ -0,0 +1,200 @@
+# frozen_string_literal: false
+require_relative 'utils'
+require 'base64'
+
+if defined?(OpenSSL::TestUtils)
+
+class OpenSSL::TestPKeyDSA < OpenSSL::PKeyTestCase
+  DSA512 = OpenSSL::TestUtils::TEST_KEY_DSA512
+
+  def test_private
+    key = OpenSSL::PKey::DSA.new(256)
+    assert(key.private?)
+    key2 = OpenSSL::PKey::DSA.new(key.to_der)
+    assert(key2.private?)
+    key3 = key.public_key
+    assert(!key3.private?)
+    key4 = OpenSSL::PKey::DSA.new(key3.to_der)
+    assert(!key4.private?)
+  end
+
+  def test_new
+    key = OpenSSL::PKey::DSA.new 256
+    pem  = key.public_key.to_pem
+    OpenSSL::PKey::DSA.new pem
+    if $0 == __FILE__
+      assert_nothing_raised {
+        key = OpenSSL::PKey::DSA.new 2048
+      }
+    end
+  end
+
+  def test_new_break
+    assert_nil(OpenSSL::PKey::DSA.new(512) { break })
+    assert_raise(RuntimeError) do
+      OpenSSL::PKey::DSA.new(512) { raise }
+    end
+  end
+
+  def test_sign_verify
+    data = "Sign me!"
+    if defined?(OpenSSL::Digest::DSS1)
+      signature = DSA512.sign(OpenSSL::Digest::DSS1.new, data)
+      assert_equal true, DSA512.verify(OpenSSL::Digest::DSS1.new, signature, data)
+    end
+
+    return if OpenSSL::OPENSSL_VERSION_NUMBER <= 0x010000000
+    signature = DSA512.sign("SHA1", data)
+    assert_equal true, DSA512.verify("SHA1", signature, data)
+
+    signature0 = (<<~'end;').unpack("m")[0]
+      MCwCFH5h40plgU5Fh0Z4wvEEpz0eE9SnAhRPbkRB8ggsN/vsSEYMXvJwjGg/
+      6g==
+    end;
+    assert_equal true, DSA512.verify("SHA256", signature0, data)
+    signature1 = signature0.succ
+    assert_equal false, DSA512.verify("SHA256", signature1, data)
+  end
+
+  def test_sys_sign_verify
+    key = OpenSSL::TestUtils::TEST_KEY_DSA256
+    data = 'Sign me!'
+    digest = OpenSSL::Digest::SHA1.digest(data)
+    sig = key.syssign(digest)
+    assert(key.sysverify(digest, sig))
+  end
+
+  def test_DSAPrivateKey
+    # OpenSSL DSAPrivateKey format; similar to RSAPrivateKey
+    asn1 = OpenSSL::ASN1::Sequence([
+      OpenSSL::ASN1::Integer(0),
+      OpenSSL::ASN1::Integer(DSA512.p),
+      OpenSSL::ASN1::Integer(DSA512.q),
+      OpenSSL::ASN1::Integer(DSA512.g),
+      OpenSSL::ASN1::Integer(DSA512.pub_key),
+      OpenSSL::ASN1::Integer(DSA512.priv_key)
+    ])
+    key = OpenSSL::PKey::DSA.new(asn1.to_der)
+    assert_predicate key, :private?
+    assert_same_dsa DSA512, key
+
+    pem = <<~EOF
+    -----BEGIN DSA PRIVATE KEY-----
+    MIH4AgEAAkEA5lB4GvEwjrsMlGDqGsxrbqeFRh6o9OWt6FgTYiEEHaOYhkIxv0Ok
+    RZPDNwOG997mDjBnvDJ1i56OmS3MbTnovwIVAJgub/aDrSDB4DZGH7UyarcaGy6D
+    AkB9HdFw/3td8K4l1FZHv7TCZeJ3ZLb7dF3TWoGUP003RCqoji3/lHdKoVdTQNuR
+    S/m6DlCwhjRjiQ/lBRgCLCcaAkEAjN891JBjzpMj4bWgsACmMggFf57DS0Ti+5++
+    Q1VB8qkJN7rA7/2HrCR3gTsWNb1YhAsnFsoeRscC+LxXoXi9OAIUBG98h4tilg6S
+    55jreJD3Se3slps=
+    -----END DSA PRIVATE KEY-----
+    EOF
+    key = OpenSSL::PKey::DSA.new(pem)
+    assert_same_dsa DSA512, key
+
+    assert_equal asn1.to_der, DSA512.to_der
+    assert_equal pem, DSA512.export
+  end
+
+  def test_DSAPrivateKey_encrypted
+    # key = abcdef
+    pem = <<~EOF
+    -----BEGIN DSA PRIVATE KEY-----
+    Proc-Type: 4,ENCRYPTED
+    DEK-Info: AES-128-CBC,F8BB7BFC7EAB9118AC2E3DA16C8DB1D9
+
+    D2sIzsM9MLXBtlF4RW42u2GB9gX3HQ3prtVIjWPLaKBYoToRUiv8WKsjptfZuLSB
+    74ZPdMS7VITM+W1HIxo/tjS80348Cwc9ou8H/E6WGat8ZUk/igLOUEII+coQS6qw
+    QpuLMcCIavevX0gjdjEIkojBB81TYDofA1Bp1z1zDI/2Zhw822xapI79ZF7Rmywt
+    OSyWzFaGipgDpdFsGzvT6//z0jMr0AuJVcZ0VJ5lyPGQZAeVBlbYEI4T72cC5Cz7
+    XvLiaUtum6/sASD2PQqdDNpgx/WA6Vs1Po2kIUQIM5TIwyJI0GdykZcYm6xIK/ta
+    Wgx6c8K+qBAIVrilw3EWxw==
+    -----END DSA PRIVATE KEY-----
+    EOF
+    key = OpenSSL::PKey::DSA.new(pem, "abcdef")
+    assert_same_dsa DSA512, key
+    key = OpenSSL::PKey::DSA.new(pem) { "abcdef" }
+    assert_same_dsa DSA512, key
+
+    cipher = OpenSSL::Cipher.new("aes-128-cbc")
+    exported = DSA512.to_pem(cipher, "abcdef\0\1")
+    assert_same_dsa DSA512, OpenSSL::PKey::DSA.new(exported, "abcdef\0\1")
+    assert_raise(OpenSSL::PKey::DSAError) {
+      OpenSSL::PKey::DSA.new(exported, "abcdef")
+    }
+  end
+
+  def test_PUBKEY
+    asn1 = OpenSSL::ASN1::Sequence([
+      OpenSSL::ASN1::Sequence([
+        OpenSSL::ASN1::ObjectId("DSA"),
+        OpenSSL::ASN1::Sequence([
+          OpenSSL::ASN1::Integer(DSA512.p),
+          OpenSSL::ASN1::Integer(DSA512.q),
+          OpenSSL::ASN1::Integer(DSA512.g)
+        ])
+      ]),
+      OpenSSL::ASN1::BitString(
+        OpenSSL::ASN1::Integer(DSA512.pub_key).to_der
+      )
+    ])
+    key = OpenSSL::PKey::DSA.new(asn1.to_der)
+    assert_not_predicate key, :private?
+    assert_same_dsa dup_public(DSA512), key
+
+    pem = <<~EOF
+    -----BEGIN PUBLIC KEY-----
+    MIHxMIGoBgcqhkjOOAQBMIGcAkEA5lB4GvEwjrsMlGDqGsxrbqeFRh6o9OWt6FgT
+    YiEEHaOYhkIxv0OkRZPDNwOG997mDjBnvDJ1i56OmS3MbTnovwIVAJgub/aDrSDB
+    4DZGH7UyarcaGy6DAkB9HdFw/3td8K4l1FZHv7TCZeJ3ZLb7dF3TWoGUP003RCqo
+    ji3/lHdKoVdTQNuRS/m6DlCwhjRjiQ/lBRgCLCcaA0QAAkEAjN891JBjzpMj4bWg
+    sACmMggFf57DS0Ti+5++Q1VB8qkJN7rA7/2HrCR3gTsWNb1YhAsnFsoeRscC+LxX
+    oXi9OA==
+    -----END PUBLIC KEY-----
+    EOF
+    key = OpenSSL::PKey::DSA.new(pem)
+    assert_same_dsa dup_public(DSA512), key
+
+    assert_equal asn1.to_der, dup_public(DSA512).to_der
+    assert_equal pem, dup_public(DSA512).export
+  end
+
+  def test_read_DSAPublicKey_pem
+    # TODO: where is the standard? PKey::DSA.new can read only PEM
+    p = 12260055936871293565827712385212529106400444521449663325576634579961635627321079536132296996623400607469624537382977152381984332395192110731059176842635699
+    q = 979494906553787301107832405790107343409973851677
+    g = 3731695366899846297271147240305742456317979984190506040697507048095553842519347835107669437969086119948785140453492839427038591924536131566350847469993845
+    y = 10505239074982761504240823422422813362721498896040719759460296306305851824586095328615844661273887569281276387605297130014564808567159023649684010036304695
+    pem = <<-EOF
+-----BEGIN DSA PUBLIC KEY-----
+MIHfAkEAyJSJ+g+P/knVcgDwwTzC7Pwg/pWs2EMd/r+lYlXhNfzg0biuXRul8VR4
+VUC/phySExY0PdcqItkR/xYAYNMbNwJBAOoV57X0FxKO/PrNa/MkoWzkCKV/hzhE
+p0zbFdsicw+hIjJ7S6Sd/FlDlo89HQZ2FuvWJ6wGLM1j00r39+F2qbMCFQCrkhIX
+SG+is37hz1IaBeEudjB2HQJAR0AloavBvtsng8obsjLb7EKnB+pSeHr/BdIQ3VH7
+fWLOqqkzFeRrYMDzUpl36XktY6Yq8EJYlW9pCMmBVNy/dQ==
+-----END DSA PUBLIC KEY-----
+    EOF
+    key = OpenSSL::PKey::DSA.new(pem)
+    assert(key.public?)
+    assert(!key.private?)
+    assert_equal(p, key.p)
+    assert_equal(q, key.q)
+    assert_equal(g, key.g)
+    assert_equal(y, key.pub_key)
+    assert_equal(nil, key.priv_key)
+  end
+
+  def test_dup
+    key = OpenSSL::PKey::DSA.new(256)
+    key2 = key.dup
+    assert_equal key.params, key2.params
+    key2.set_pqg(key2.p + 1, key2.q, key2.g)
+    assert_not_equal key.params, key2.params
+  end
+
+  private
+  def assert_same_dsa(expected, key)
+    check_component(expected, key, [:p, :q, :g, :pub_key, :priv_key])
+  end
+end
+
+end
diff --git a/test/test_pkey_ec.rb b/test/test_pkey_ec.rb
new file mode 100644
index 0000000..c549d9c
--- /dev/null
+++ b/test/test_pkey_ec.rb
@@ -0,0 +1,329 @@
+# frozen_string_literal: false
+require_relative 'utils'
+
+if defined?(OpenSSL::TestUtils) && defined?(OpenSSL::PKey::EC)
+
+class OpenSSL::TestEC < OpenSSL::PKeyTestCase
+  P256 = OpenSSL::TestUtils::TEST_KEY_EC_P256V1
+
+  def test_ec_key
+    builtin_curves = OpenSSL::PKey::EC.builtin_curves
+    assert_not_empty builtin_curves
+
+    builtin_curves.each do |curve_name, comment|
+      # Oakley curves and X25519 are not suitable for signing and causes
+      # FIPS-selftest failure on some environment, so skip for now.
+      next if ["Oakley", "X25519"].any? { |n| curve_name.start_with?(n) }
+
+      key = OpenSSL::PKey::EC.new(curve_name)
+      key.generate_key!
+
+      assert_predicate key, :private?
+      assert_predicate key, :public?
+      assert_nothing_raised { key.check_key }
+    end
+
+    key1 = OpenSSL::PKey::EC.new("prime256v1").generate_key!
+
+    key2 = OpenSSL::PKey::EC.new
+    key2.group = key1.group
+    key2.private_key = key1.private_key
+    key2.public_key = key1.public_key
+    assert_equal key1.to_der, key2.to_der
+
+    key3 = OpenSSL::PKey::EC.new(key1)
+    assert_equal key1.to_der, key3.to_der
+
+    key4 = OpenSSL::PKey::EC.new(key1.to_der)
+    assert_equal key1.to_der, key4.to_der
+
+    key5 = key1.dup
+    assert_equal key1.to_der, key5.to_der
+    key_tmp = OpenSSL::PKey::EC.new("prime256v1").generate_key!
+    key5.private_key = key_tmp.private_key
+    key5.public_key = key_tmp.public_key
+    assert_not_equal key1.to_der, key5.to_der
+  end
+
+  def test_generate
+    assert_raise(OpenSSL::PKey::ECError) { OpenSSL::PKey::EC.generate("non-existent") }
+    g = OpenSSL::PKey::EC::Group.new("prime256v1")
+    ec = OpenSSL::PKey::EC.generate(g)
+    assert_equal(true, ec.private?)
+    ec = OpenSSL::PKey::EC.generate("prime256v1")
+    assert_equal(true, ec.private?)
+  end
+
+  def test_check_key
+    key = OpenSSL::PKey::EC.new("prime256v1").generate_key!
+    assert_equal(true, key.check_key)
+    assert_equal(true, key.private?)
+    assert_equal(true, key.public?)
+    key2 = OpenSSL::PKey::EC.new(key.group)
+    assert_equal(false, key2.private?)
+    assert_equal(false, key2.public?)
+    key2.public_key = key.public_key
+    assert_equal(false, key2.private?)
+    assert_equal(true, key2.public?)
+    key2.private_key = key.private_key
+    assert_equal(true, key2.private?)
+    assert_equal(true, key2.public?)
+    assert_equal(true, key2.check_key)
+    key2.private_key += 1
+    assert_raise(OpenSSL::PKey::ECError) { key2.check_key }
+  end
+
+  def test_sign_verify
+    data = "Sign me!"
+    signature = P256.sign("SHA1", data)
+    assert_equal true, P256.verify("SHA1", signature, data)
+
+    signature0 = (<<~'end;').unpack("m")[0]
+      MEQCIEOTY/hD7eI8a0qlzxkIt8LLZ8uwiaSfVbjX2dPAvN11AiAQdCYx56Fq
+      QdBp1B4sxJoA8jvODMMklMyBKVmudboA6A==
+    end;
+    assert_equal true, P256.verify("SHA256", signature0, data)
+    signature1 = signature0.succ
+    assert_equal false, P256.verify("SHA256", signature1, data)
+  end
+
+  def test_dsa_sign_verify
+    data1 = "foo"
+    data2 = "bar"
+    key = OpenSSL::PKey::EC.new("prime256v1").generate_key!
+    sig = key.dsa_sign_asn1(data1)
+    assert_equal true, key.dsa_verify_asn1(data1, sig)
+    assert_equal false, key.dsa_verify_asn1(data2, sig)
+  end
+
+  def test_dsa_sign_asn1_FIPS186_3
+    key = OpenSSL::PKey::EC.new("prime256v1").generate_key!
+    size = key.group.order.num_bits / 8 + 1
+    dgst = (1..size).to_a.pack('C*')
+    begin
+      sig = key.dsa_sign_asn1(dgst)
+      # dgst is auto-truncated according to FIPS186-3 after openssl-0.9.8m
+      assert(key.dsa_verify_asn1(dgst + "garbage", sig))
+    rescue OpenSSL::PKey::ECError => e
+      # just an exception for longer dgst before openssl-0.9.8m
+      assert_equal('ECDSA_sign: data too large for key size', e.message)
+      # no need to do following tests
+      return
+    end
+  end
+
+  def test_dh_compute_key
+    key_a = OpenSSL::PKey::EC.new("prime256v1").generate_key!
+    key_b = OpenSSL::PKey::EC.new(key_a.group).generate_key!
+
+    pub_a = key_a.public_key
+    pub_b = key_b.public_key
+    a = key_a.dh_compute_key(pub_b)
+    b = key_b.dh_compute_key(pub_a)
+    assert_equal a, b
+  end
+
+  def test_ECPrivateKey
+    asn1 = OpenSSL::ASN1::Sequence([
+      OpenSSL::ASN1::Integer(1),
+      OpenSSL::ASN1::OctetString(P256.private_key.to_s(2)),
+      OpenSSL::ASN1::ASN1Data.new(
+        [OpenSSL::ASN1::ObjectId("prime256v1")],
+        0, :CONTEXT_SPECIFIC
+      ),
+      OpenSSL::ASN1::ASN1Data.new(
+        [OpenSSL::ASN1::BitString(P256.public_key.to_bn.to_s(2))],
+        1, :CONTEXT_SPECIFIC
+      )
+    ])
+    key = OpenSSL::PKey::EC.new(asn1.to_der)
+    assert_predicate key, :private?
+    assert_same_ec P256, key
+
+    pem = <<~EOF
+    -----BEGIN EC PRIVATE KEY-----
+    MHcCAQEEIID49FDqcf1O1eO8saTgG70UbXQw9Fqwseliit2aWhH1oAoGCCqGSM49
+    AwEHoUQDQgAEFglk2c+oVUIKQ64eZG9bhLNPWB7lSZ/ArK41eGy5wAzU/0G51Xtt
+    CeBUl+MahZtn9fO1JKdF4qJmS39dXnpENg==
+    -----END EC PRIVATE KEY-----
+    EOF
+    key = OpenSSL::PKey::EC.new(pem)
+    assert_same_ec P256, key
+
+    assert_equal asn1.to_der, P256.to_der
+    assert_equal pem, P256.export
+  end
+
+  def test_ECPrivateKey_encrypted
+    # key = abcdef
+    pem = <<~EOF
+    -----BEGIN EC PRIVATE KEY-----
+    Proc-Type: 4,ENCRYPTED
+    DEK-Info: AES-128-CBC,85743EB6FAC9EA76BF99D9328AFD1A66
+
+    nhsP1NHxb53aeZdzUe9umKKyr+OIwQq67eP0ONM6E1vFTIcjkDcFLR6PhPFufF4m
+    y7E2HF+9uT1KPQhlE+D63i1m1Mvez6PWfNM34iOQp2vEhaoHHKlR3c43lLyzaZDI
+    0/dGSU5SzFG+iT9iFXCwCvv+bxyegkBOyALFje1NAsM=
+    -----END EC PRIVATE KEY-----
+    EOF
+    key = OpenSSL::PKey::EC.new(pem, "abcdef")
+    assert_same_ec P256, key
+    key = OpenSSL::PKey::EC.new(pem) { "abcdef" }
+    assert_same_ec P256, key
+
+    cipher = OpenSSL::Cipher.new("aes-128-cbc")
+    exported = P256.to_pem(cipher, "abcdef\0\1")
+    assert_same_ec P256, OpenSSL::PKey::EC.new(exported, "abcdef\0\1")
+    assert_raise(OpenSSL::PKey::ECError) {
+      OpenSSL::PKey::EC.new(exported, "abcdef")
+    }
+  end
+
+  def test_PUBKEY
+    asn1 = OpenSSL::ASN1::Sequence([
+      OpenSSL::ASN1::Sequence([
+        OpenSSL::ASN1::ObjectId("id-ecPublicKey"),
+        OpenSSL::ASN1::ObjectId("prime256v1")
+      ]),
+      OpenSSL::ASN1::BitString(
+        P256.public_key.to_bn.to_s(2)
+      )
+    ])
+    key = OpenSSL::PKey::EC.new(asn1.to_der)
+    assert_not_predicate key, :private?
+    assert_same_ec dup_public(P256), key
+
+    pem = <<~EOF
+    -----BEGIN PUBLIC KEY-----
+    MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFglk2c+oVUIKQ64eZG9bhLNPWB7l
+    SZ/ArK41eGy5wAzU/0G51XttCeBUl+MahZtn9fO1JKdF4qJmS39dXnpENg==
+    -----END PUBLIC KEY-----
+    EOF
+    key = OpenSSL::PKey::EC.new(pem)
+    assert_same_ec dup_public(P256), key
+
+    assert_equal asn1.to_der, dup_public(P256).to_der
+    assert_equal pem, dup_public(P256).export
+  end
+
+  def test_ec_group
+    group1 = OpenSSL::PKey::EC::Group.new("prime256v1")
+    key1 = OpenSSL::PKey::EC.new(group1)
+    assert_equal group1, key1.group
+
+    group2 = OpenSSL::PKey::EC::Group.new(group1)
+    assert_equal group1.to_der, group2.to_der
+    assert_equal group1, group2
+    group2.asn1_flag ^=OpenSSL::PKey::EC::NAMED_CURVE
+    assert_not_equal group1.to_der, group2.to_der
+    assert_equal group1, group2
+
+    group3 = group1.dup
+    assert_equal group1.to_der, group3.to_der
+
+    assert group1.asn1_flag & OpenSSL::PKey::EC::NAMED_CURVE # our default
+    der = group1.to_der
+    group4 = OpenSSL::PKey::EC::Group.new(der)
+    group1.point_conversion_form = group4.point_conversion_form = :uncompressed
+    assert_equal :uncompressed, group1.point_conversion_form
+    assert_equal :uncompressed, group4.point_conversion_form
+    assert_equal group1, group4
+    assert_equal group1.curve_name, group4.curve_name
+    assert_equal group1.generator.to_bn, group4.generator.to_bn
+    assert_equal group1.order, group4.order
+    assert_equal group1.cofactor, group4.cofactor
+    assert_equal group1.seed, group4.seed
+    assert_equal group1.degree, group4.degree
+  end
+
+  def test_ec_point
+    group = OpenSSL::PKey::EC::Group.new("prime256v1")
+    key = OpenSSL::PKey::EC.new(group).generate_key!
+    point = key.public_key
+
+    point2 = OpenSSL::PKey::EC::Point.new(group, point.to_bn)
+    assert_equal point, point2
+    assert_equal point.to_bn, point2.to_bn
+    point2.invert!
+    assert_not_equal point.to_bn, point2.to_bn
+
+    begin
+      group = OpenSSL::PKey::EC::Group.new(:GFp, 17, 2, 2)
+      group.point_conversion_form = :uncompressed
+      generator = OpenSSL::PKey::EC::Point.new(group, 0x040501.to_bn)
+      group.set_generator(generator, 19, 1)
+      point = OpenSSL::PKey::EC::Point.new(group, 0x040603.to_bn)
+    rescue OpenSSL::PKey::EC::Group::Error
+      pend "Patched OpenSSL rejected curve" if /unsupported field/ =~ $!.message
+      raise
+    end
+
+    assert_equal 0x040603.to_bn, point.to_bn(:uncompressed)
+    assert_equal 0x0306.to_bn, point.to_bn(:compressed)
+    assert_equal 0x070603.to_bn, point.to_bn(:hybrid)
+
+    assert_equal 0x040603.to_bn, point.to_bn
+    assert_equal true, point.on_curve?
+    point.invert! # 8.5
+    assert_equal 0x04060E.to_bn, point.to_bn
+    assert_equal true, point.on_curve?
+
+    assert_equal false, point.infinity?
+    point.set_to_infinity!
+    assert_equal true, point.infinity?
+    assert_equal 0.to_bn, point.to_bn
+    assert_equal true, point.on_curve?
+  end
+
+  def test_ec_point_mul
+    begin
+      # y^2 = x^3 + 2x + 2 over F_17
+      # generator is (5, 1)
+      group = OpenSSL::PKey::EC::Group.new(:GFp, 17, 2, 2)
+      group.point_conversion_form = :uncompressed
+      gen = OpenSSL::PKey::EC::Point.new(group, OpenSSL::BN.new("040501", 16))
+      group.set_generator(gen, 19, 1)
+
+      # 3 * (6, 3) = (16, 13)
+      point_a = OpenSSL::PKey::EC::Point.new(group, OpenSSL::BN.new("040603", 16))
+      result_a1 = point_a.mul(3)
+      assert_equal("04100D", result_a1.to_bn.to_s(16))
+      # 3 * (6, 3) + 3 * (5, 1) = (7, 6)
+      result_a2 = point_a.mul(3, 3)
+      assert_equal("040706", result_a2.to_bn.to_s(16))
+      # 3 * point_a = 3 * (6, 3) = (16, 13)
+      result_b1 = point_a.mul([3], [])
+      assert_equal("04100D", result_b1.to_bn.to_s(16))
+      # 3 * point_a + 2 * point_a = 3 * (6, 3) + 2 * (6, 3) = (7, 11)
+      result_b1 = point_a.mul([3, 2], [point_a])
+      assert_equal("04070B", result_b1.to_bn.to_s(16))
+      # 3 * point_a + 5 * point_a.group.generator = 3 * (6, 3) + 5 * (5, 1) = (13, 10)
+      result_b1 = point_a.mul([3], [], 5)
+      assert_equal("040D0A", result_b1.to_bn.to_s(16))
+    rescue OpenSSL::PKey::EC::Group::Error
+      # CentOS patches OpenSSL to reject curves defined over Fp where p < 256 bits
+      raise if $!.message !~ /unsupported field/
+    end
+
+    p256_key = P256
+    p256_g = p256_key.group
+    assert_equal(p256_key.public_key, p256_g.generator.mul(p256_key.private_key))
+
+    # invalid argument
+    point = p256_key.public_key
+    assert_raise(TypeError) { point.mul(nil) }
+    assert_raise(ArgumentError) { point.mul([1], [point]) }
+    assert_raise(TypeError) { point.mul([1], nil) }
+    assert_raise(TypeError) { point.mul([nil], []) }
+  end
+
+# test Group: asn1_flag, point_conversion
+
+  private
+
+  def assert_same_ec(expected, key)
+    check_component(expected, key, [:group, :public_key, :private_key])
+  end
+end
+
+end
diff --git a/test/test_pkey_rsa.rb b/test/test_pkey_rsa.rb
new file mode 100644
index 0000000..b24f1d5
--- /dev/null
+++ b/test/test_pkey_rsa.rb
@@ -0,0 +1,259 @@
+# frozen_string_literal: false
+require_relative 'utils'
+require 'base64'
+
+if defined?(OpenSSL::TestUtils)
+
+class OpenSSL::TestPKeyRSA < OpenSSL::PKeyTestCase
+  RSA1024 = OpenSSL::TestUtils::TEST_KEY_RSA1024
+
+  def test_padding
+    key = OpenSSL::PKey::RSA.new(512, 3)
+
+    # Need right size for raw mode
+    plain0 = "x" * (512/8)
+    cipher = key.private_encrypt(plain0, OpenSSL::PKey::RSA::NO_PADDING)
+    plain1 = key.public_decrypt(cipher, OpenSSL::PKey::RSA::NO_PADDING)
+    assert_equal(plain0, plain1)
+
+    # Need smaller size for pkcs1 mode
+    plain0 = "x" * (512/8 - 11)
+    cipher1 = key.private_encrypt(plain0, OpenSSL::PKey::RSA::PKCS1_PADDING)
+    plain1 = key.public_decrypt(cipher1, OpenSSL::PKey::RSA::PKCS1_PADDING)
+    assert_equal(plain0, plain1)
+
+    cipherdef = key.private_encrypt(plain0) # PKCS1_PADDING is default
+    plain1 = key.public_decrypt(cipherdef)
+    assert_equal(plain0, plain1)
+    assert_equal(cipher1, cipherdef)
+
+    # Failure cases
+    assert_raise(ArgumentError){ key.private_encrypt() }
+    assert_raise(ArgumentError){ key.private_encrypt("hi", 1, nil) }
+    assert_raise(OpenSSL::PKey::RSAError){ key.private_encrypt(plain0, 666) }
+  end
+
+  def test_private
+    key = OpenSSL::PKey::RSA.new(512, 3)
+    assert(key.private?)
+    key2 = OpenSSL::PKey::RSA.new(key.to_der)
+    assert(key2.private?)
+    key3 = key.public_key
+    assert(!key3.private?)
+    key4 = OpenSSL::PKey::RSA.new(key3.to_der)
+    assert(!key4.private?)
+  end
+
+  def test_new
+    key = OpenSSL::PKey::RSA.new 512
+    pem  = key.public_key.to_pem
+    OpenSSL::PKey::RSA.new pem
+    assert_equal([], OpenSSL.errors)
+  end
+
+  def test_new_exponent_default
+    assert_equal(65537, OpenSSL::PKey::RSA.new(512).e)
+  end
+
+  def test_new_with_exponent
+    1.upto(30) do |idx|
+      e = (2 ** idx) + 1
+      key = OpenSSL::PKey::RSA.new(512, e)
+      assert_equal(e, key.e)
+    end
+  end
+
+  def test_new_break
+    assert_nil(OpenSSL::PKey::RSA.new(1024) { break })
+    assert_raise(RuntimeError) do
+      OpenSSL::PKey::RSA.new(1024) { raise }
+    end
+  end
+
+  def test_sign_verify
+    data = "Sign me!"
+    signature = RSA1024.sign("SHA1", data)
+    assert_equal true, RSA1024.verify("SHA1", signature, data)
+
+    signature0 = (<<~'end;').unpack("m")[0]
+      oLCgbprPvfhM4pjFQiDTFeWI9Sk+Og7Nh9TmIZ/xSxf2CGXQrptlwo7NQ28+
+      WA6YQo8jPH4hSuyWIM4Gz4qRYiYRkl5TDMUYob94zm8Si1HxEiS9354tzvqS
+      zS8MLW2BtNPuTubMxTItHGTnOzo9sUg0LAHVFt8kHG2NfKAw/gQ=
+    end;
+    assert_equal true, RSA1024.verify("SHA256", signature0, data)
+    signature1 = signature0.succ
+    assert_equal false, RSA1024.verify("SHA256", signature1, data)
+  end
+
+  def test_digest_state_irrelevant_sign
+    key = RSA1024
+    digest1 = OpenSSL::Digest::SHA1.new
+    digest2 = OpenSSL::Digest::SHA1.new
+    data = 'Sign me!'
+    digest1 << 'Change state of digest1'
+    sig1 = key.sign(digest1, data)
+    sig2 = key.sign(digest2, data)
+    assert_equal(sig1, sig2)
+  end
+
+  def test_digest_state_irrelevant_verify
+    key = RSA1024
+    digest1 = OpenSSL::Digest::SHA1.new
+    digest2 = OpenSSL::Digest::SHA1.new
+    data = 'Sign me!'
+    sig = key.sign(digest1, data)
+    digest1.reset
+    digest1 << 'Change state of digest1'
+    assert(key.verify(digest1, sig, data))
+    assert(key.verify(digest2, sig, data))
+  end
+
+  def test_verify_empty_rsa
+    rsa = OpenSSL::PKey::RSA.new
+    assert_raise(OpenSSL::PKey::PKeyError, "[Bug #12783]") {
+      rsa.verify("SHA1", "a", "b")
+    }
+  end
+
+  def test_RSAPrivateKey
+    asn1 = OpenSSL::ASN1::Sequence([
+      OpenSSL::ASN1::Integer(0),
+      OpenSSL::ASN1::Integer(RSA1024.n),
+      OpenSSL::ASN1::Integer(RSA1024.e),
+      OpenSSL::ASN1::Integer(RSA1024.d),
+      OpenSSL::ASN1::Integer(RSA1024.p),
+      OpenSSL::ASN1::Integer(RSA1024.q),
+      OpenSSL::ASN1::Integer(RSA1024.dmp1),
+      OpenSSL::ASN1::Integer(RSA1024.dmq1),
+      OpenSSL::ASN1::Integer(RSA1024.iqmp)
+    ])
+    key = OpenSSL::PKey::RSA.new(asn1.to_der)
+    assert_predicate key, :private?
+    assert_same_rsa RSA1024, key
+
+    pem = <<~EOF
+    -----BEGIN RSA PRIVATE KEY-----
+    MIICXgIBAAKBgQDLwsSw1ECnPtT+PkOgHhcGA71nwC2/nL85VBGnRqDxOqjVh7Cx
+    aKPERYHsk4BPCkE3brtThPWc9kjHEQQ7uf9Y1rbCz0layNqHyywQEVLFmp1cpIt/
+    Q3geLv8ZD9pihowKJDyMDiN6ArYUmZczvW4976MU3+l54E6lF/JfFEU5hwIDAQAB
+    AoGBAKSl/MQarye1yOysqX6P8fDFQt68VvtXkNmlSiKOGuzyho0M+UVSFcs6k1L0
+    maDE25AMZUiGzuWHyaU55d7RXDgeskDMakD1v6ZejYtxJkSXbETOTLDwUWTn618T
+    gnb17tU1jktUtU67xK/08i/XodlgnQhs6VoHTuCh3Hu77O6RAkEA7+gxqBuZR572
+    74/akiW/SuXm0SXPEviyO1MuSRwtI87B02D0qgV8D1UHRm4AhMnJ8MCs1809kMQE
+    JiQUCrp9mQJBANlt2ngBO14us6NnhuAseFDTBzCHXwUUu1YKHpMMmxpnGqaldGgX
+    sOZB3lgJsT9VlGf3YGYdkLTNVbogQKlKpB8CQQDiSwkb4vyQfDe8/NpU5Not0fII
+    8jsDUCb+opWUTMmfbxWRR3FBNu8wnym/m19N4fFj8LqYzHX4KY0oVPu6qvJxAkEA
+    wa5snNekFcqONLIE4G5cosrIrb74sqL8GbGb+KuTAprzj5z1K8Bm0UW9lTjVDjDi
+    qRYgZfZSL+x1P/54+xTFSwJAY1FxA/N3QPCXCjPh5YqFxAMQs2VVYTfg+t0MEcJD
+    dPMQD5JX6g5HKnHFg2mZtoXQrWmJSn7p8GJK8yNTopEErA==
+    -----END RSA PRIVATE KEY-----
+    EOF
+    key = OpenSSL::PKey::RSA.new(pem)
+    assert_same_rsa RSA1024, key
+
+    assert_equal asn1.to_der, RSA1024.to_der
+    assert_equal pem, RSA1024.export
+  end
+
+  def test_RSAPrivateKey_encrypted
+    # key = abcdef
+    pem = <<~EOF
+    -----BEGIN RSA PRIVATE KEY-----
+    Proc-Type: 4,ENCRYPTED
+    DEK-Info: AES-128-CBC,733F5302505B34701FC41F5C0746E4C0
+
+    zgJniZZQfvv8TFx3LzV6zhAQVayvQVZlAYqFq2yWbbxzF7C+IBhKQle9IhUQ9j/y
+    /jkvol550LS8vZ7TX5WxyDLe12cdqzEvpR6jf3NbxiNysOCxwG4ErhaZGP+krcoB
+    ObuL0nvls/+3myy5reKEyy22+0GvTDjaChfr+FwJjXMG+IBCLscYdgZC1LQL6oAn
+    9xY5DH3W7BW4wR5ttxvtN32TkfVQh8xi3jrLrduUh+hV8DTiAiLIhv0Vykwhep2p
+    WZA+7qbrYaYM8GLLgLrb6LfBoxeNxAEKiTpl1quFkm+Hk1dKq0EhVnxHf92x0zVF
+    jRGZxAMNcrlCoE4f5XK45epVZSZvihdo1k73GPbp84aZ5P/xlO4OwZ3i4uCQXynl
+    jE9c+I+4rRWKyPz9gkkqo0+teJL8ifeKt/3ab6FcdA0aArynqmsKJMktxmNu83We
+    YVGEHZPeOlyOQqPvZqWsLnXQUfg54OkbuV4/4mWSIzxFXdFy/AekSeJugpswMXqn
+    oNck4qySNyfnlyelppXyWWwDfVus9CVAGZmJQaJExHMT/rQFRVchlmY0Ddr5O264
+    gcjv90o1NBOc2fNcqjivuoX7ROqys4K/YdNQ1HhQ7usJghADNOtuLI8ZqMh9akXD
+    Eqp6Ne97wq1NiJj0nt3SJlzTnOyTjzrTe0Y+atPkVKp7SsjkATMI9JdhXwGhWd7a
+    qFVl0owZiDasgEhyG2K5L6r+yaJLYkPVXZYC/wtWC3NEchnDWZGQcXzB4xROCQkD
+    OlWNYDkPiZioeFkA3/fTMvG4moB2Pp9Q4GU5fJ6k43Ccu1up8dX/LumZb4ecg5/x
+    -----END RSA PRIVATE KEY-----
+    EOF
+    key = OpenSSL::PKey::RSA.new(pem, "abcdef")
+    assert_same_rsa RSA1024, key
+    key = OpenSSL::PKey::RSA.new(pem) { "abcdef" }
+    assert_same_rsa RSA1024, key
+
+    cipher = OpenSSL::Cipher.new("aes-128-cbc")
+    exported = RSA1024.to_pem(cipher, "abcdef\0\1")
+    assert_same_rsa RSA1024, OpenSSL::PKey::RSA.new(exported, "abcdef\0\1")
+    assert_raise(OpenSSL::PKey::RSAError) {
+      OpenSSL::PKey::RSA.new(exported, "abcdef")
+    }
+  end
+
+  def test_RSAPublicKey
+    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 dup_public(RSA1024), 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 dup_public(RSA1024), key
+  end
+
+  def test_PUBKEY
+    asn1 = OpenSSL::ASN1::Sequence([
+      OpenSSL::ASN1::Sequence([
+        OpenSSL::ASN1::ObjectId("rsaEncryption"),
+        OpenSSL::ASN1::Null(nil)
+      ]),
+      OpenSSL::ASN1::BitString(
+        OpenSSL::ASN1::Sequence([
+          OpenSSL::ASN1::Integer(RSA1024.n),
+          OpenSSL::ASN1::Integer(RSA1024.e)
+        ]).to_der
+      )
+    ])
+    key = OpenSSL::PKey::RSA.new(asn1.to_der)
+    assert_not_predicate key, :private?
+    assert_same_rsa dup_public(RSA1024), key
+
+    pem = <<~EOF
+    -----BEGIN PUBLIC KEY-----
+    MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDLwsSw1ECnPtT+PkOgHhcGA71n
+    wC2/nL85VBGnRqDxOqjVh7CxaKPERYHsk4BPCkE3brtThPWc9kjHEQQ7uf9Y1rbC
+    z0layNqHyywQEVLFmp1cpIt/Q3geLv8ZD9pihowKJDyMDiN6ArYUmZczvW4976MU
+    3+l54E6lF/JfFEU5hwIDAQAB
+    -----END PUBLIC KEY-----
+    EOF
+    key = OpenSSL::PKey::RSA.new(pem)
+    assert_same_rsa dup_public(RSA1024), key
+
+    assert_equal asn1.to_der, dup_public(RSA1024).to_der
+    assert_equal pem, dup_public(RSA1024).export
+  end
+
+  def test_dup
+    key = OpenSSL::PKey::RSA.generate(256, 17)
+    key2 = key.dup
+    assert_equal key.params, key2.params
+    key2.set_key(key2.n, 3, key2.d)
+    assert_not_equal key.params, key2.params
+  end
+
+  private
+  def assert_same_rsa(expected, key)
+    check_component(expected, key, [:n, :e, :d, :p, :q, :dmp1, :dmq1, :iqmp])
+  end
+end
+
+end
diff --git a/test/test_random.rb b/test/test_random.rb
new file mode 100644
index 0000000..6079461
--- /dev/null
+++ b/test/test_random.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: false
+require_relative "utils"
+
+class OpenSSL::TestRandom < OpenSSL::TestCase
+  def test_random_bytes
+    assert_equal("", OpenSSL::Random.random_bytes(0))
+    assert_equal(12, OpenSSL::Random.random_bytes(12).bytesize)
+  end
+
+  def test_pseudo_bytes
+    # deprecated as of OpenSSL 1.1.0
+    assert_equal("", OpenSSL::Random.pseudo_bytes(0))
+    assert_equal(12, OpenSSL::Random.pseudo_bytes(12).bytesize)
+  end if OpenSSL::Random.methods.include?(:pseudo_bytes)
+end if defined?(OpenSSL::TestCase)
diff --git a/test/test_ssl.rb b/test/test_ssl.rb
new file mode 100644
index 0000000..1906656
--- /dev/null
+++ b/test/test_ssl.rb
@@ -0,0 +1,1300 @@
+# frozen_string_literal: false
+require_relative "utils"
+
+if defined?(OpenSSL::TestUtils)
+
+class OpenSSL::TestSSL < OpenSSL::SSLTestCase
+
+  def test_ctx_options
+    ctx = OpenSSL::SSL::SSLContext.new
+
+    assert (OpenSSL::SSL::OP_ALL & ctx.options) == OpenSSL::SSL::OP_ALL,
+           "OP_ALL is set by default"
+    ctx.options = 4
+    assert_equal 4, ctx.options & 4
+    if ctx.options != 4
+      pend "SSL_CTX_set_options() seems to be modified by distributor"
+    end
+    ctx.options = nil
+    assert_equal OpenSSL::SSL::OP_ALL, ctx.options
+
+    assert_equal true, ctx.setup
+    assert_predicate ctx, :frozen?
+    assert_equal nil, ctx.setup
+  end
+
+  def test_ssl_with_server_cert
+    ctx_proc = -> ctx {
+      ctx.cert = @svr_cert
+      ctx.key = @svr_key
+      ctx.extra_chain_cert = [@ca_cert]
+    }
+    server_proc = -> (ctx, ssl) {
+      assert_equal @svr_cert.to_der, ssl.cert.to_der
+      assert_equal nil, ssl.peer_cert
+
+      readwrite_loop(ctx, ssl)
+    }
+    start_server(ctx_proc: ctx_proc, server_proc: server_proc) { |server, port|
+      begin
+        sock = TCPSocket.new("127.0.0.1", port)
+        ctx = OpenSSL::SSL::SSLContext.new
+        ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
+        ssl.connect
+
+        assert_equal sock, ssl.io
+        assert_equal nil, ssl.cert
+        assert_equal @svr_cert.to_der, ssl.peer_cert.to_der
+        assert_equal 2, ssl.peer_cert_chain.size
+        assert_equal @svr_cert.to_der, ssl.peer_cert_chain[0].to_der
+        assert_equal @ca_cert.to_der, ssl.peer_cert_chain[1].to_der
+      ensure
+        ssl&.close
+        sock&.close
+      end
+    }
+  end
+
+  def test_sysread_and_syswrite
+    start_server { |server, port|
+      server_connect(port) { |ssl|
+        str = "x" * 100 + "\n"
+        ssl.syswrite(str)
+        newstr = ssl.sysread(str.bytesize)
+        assert_equal(str, newstr)
+
+        buf = ""
+        ssl.syswrite(str)
+        assert_same buf, ssl.sysread(str.size, buf)
+        assert_equal(str, buf)
+      }
+    }
+  end
+
+  def test_sync_close
+    start_server { |server, port|
+      begin
+        sock = TCPSocket.new("127.0.0.1", port)
+        ssl = OpenSSL::SSL::SSLSocket.new(sock)
+        ssl.connect
+        ssl.close
+        assert_not_predicate sock, :closed?
+      ensure
+        sock&.close
+      end
+
+      begin
+        sock = TCPSocket.new("127.0.0.1", port)
+        ssl = OpenSSL::SSL::SSLSocket.new(sock)
+        ssl.sync_close = true  # !!
+        ssl.connect
+        ssl.close
+        assert_predicate sock, :closed?
+      ensure
+        sock&.close
+      end
+    }
+  end
+
+  def test_copy_stream
+    start_server do |server, port|
+      server_connect(port) do |ssl|
+        IO.pipe do |r, w|
+          str = "hello world\n"
+          w.write(str)
+          IO.copy_stream(r, ssl, str.bytesize)
+          IO.copy_stream(ssl, w, str.bytesize)
+          assert_equal str, r.read(str.bytesize)
+        end
+      end
+    end
+  end
+
+  def test_client_auth_failure
+    vflag = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
+    start_server(verify_mode: vflag, ignore_listener_error: true) { |server, port|
+      sock = TCPSocket.new("127.0.0.1", port)
+      ssl = OpenSSL::SSL::SSLSocket.new(sock)
+      ssl.sync_close = true
+      begin
+        assert_handshake_error { ssl.connect }
+      ensure
+        ssl.close
+      end
+    }
+  end
+
+  def test_client_auth_success
+    vflag = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
+    start_server(verify_mode: vflag) { |server, port|
+      ctx = OpenSSL::SSL::SSLContext.new
+      ctx.key = @cli_key
+      ctx.cert = @cli_cert
+
+      server_connect(port, ctx) { |ssl|
+        ssl.puts("foo")
+        assert_equal("foo\n", ssl.gets)
+      }
+
+      called = nil
+      ctx = OpenSSL::SSL::SSLContext.new
+      ctx.client_cert_cb = Proc.new{ |sslconn|
+        called = true
+        [@cli_cert, @cli_key]
+      }
+
+      server_connect(port, ctx) { |ssl|
+        assert(called)
+        ssl.puts("foo")
+        assert_equal("foo\n", ssl.gets)
+      }
+    }
+  end
+
+  def test_client_auth_public_key
+    vflag = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
+    start_server(verify_mode: vflag, ignore_listener_error: true) do |server, port|
+      assert_raise(ArgumentError) {
+        ctx = OpenSSL::SSL::SSLContext.new
+        ctx.key = @cli_key.public_key
+        ctx.cert = @cli_cert
+        server_connect(port, ctx) { }
+      }
+
+      ctx = OpenSSL::SSL::SSLContext.new
+      ctx.client_cert_cb = Proc.new{ |ssl|
+        [@cli_cert, @cli_key.public_key]
+      }
+      assert_handshake_error { server_connect(port, ctx) }
+    end
+  end
+
+  def test_client_ca
+    ctx_proc = Proc.new do |ctx|
+      ctx.client_ca = [@ca_cert]
+    end
+
+    vflag = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
+    start_server(verify_mode: vflag, ctx_proc: ctx_proc) { |server, port|
+      ctx = OpenSSL::SSL::SSLContext.new
+      client_ca_from_server = nil
+      ctx.client_cert_cb = Proc.new do |sslconn|
+        client_ca_from_server = sslconn.client_ca
+        [@cli_cert, @cli_key]
+      end
+      server_connect(port, ctx) { |ssl| assert_equal([@ca], client_ca_from_server) }
+    }
+  end
+
+  def test_read_nonblock_without_session
+    OpenSSL::TestUtils.silent do
+      start_server(start_immediately: false) { |server, port|
+        sock = TCPSocket.new("127.0.0.1", port)
+        ssl = OpenSSL::SSL::SSLSocket.new(sock)
+        ssl.sync_close = true
+
+        assert_equal :wait_readable, ssl.read_nonblock(100, exception: false)
+        ssl.write("abc\n")
+        IO.select [ssl]
+        assert_equal('a', ssl.read_nonblock(1))
+        assert_equal("bc\n", ssl.read_nonblock(100))
+        assert_equal :wait_readable, ssl.read_nonblock(100, exception: false)
+        ssl.close
+      }
+    end
+  end
+
+  def test_starttls
+    server_proc = -> (ctx, ssl) {
+      begin
+        while line = ssl.gets
+          if line =~ /^STARTTLS$/
+            ssl.write("x")
+            ssl.flush
+            ssl.accept
+            next
+          end
+          ssl.write(line)
+        end
+      rescue OpenSSL::SSL::SSLError
+      rescue IOError
+      ensure
+        ssl.close rescue nil
+      end
+    }
+
+    EnvUtil.suppress_warning do # read/write on not started session
+      start_server(start_immediately: false,
+                   server_proc: server_proc) { |server, port|
+        begin
+          sock = TCPSocket.new("127.0.0.1", port)
+          ssl = OpenSSL::SSL::SSLSocket.new(sock)
+
+          ssl.puts "plaintext"
+          assert_equal "plaintext\n", ssl.gets
+
+          ssl.puts("STARTTLS")
+          ssl.read(1)
+          ssl.connect
+
+          ssl.puts "over-tls"
+          assert_equal "over-tls\n", ssl.gets
+        ensure
+          ssl&.close
+          sock&.close
+        end
+      }
+    end
+  end
+
+  def test_parallel
+    start_server { |server, port|
+      ssls = []
+      10.times{
+        sock = TCPSocket.new("127.0.0.1", port)
+        ssl = OpenSSL::SSL::SSLSocket.new(sock)
+        ssl.connect
+        ssl.sync_close = true
+        ssls << ssl
+      }
+      str = "x" * 1000 + "\n"
+      ITERATIONS.times{
+        ssls.each{|ssl|
+          ssl.puts(str)
+          assert_equal(str, ssl.gets)
+        }
+      }
+      ssls.each{|ssl| ssl.close }
+    }
+  end
+
+  def test_verify_result
+    start_server(ignore_listener_error: true) { |server, port|
+      sock = TCPSocket.new("127.0.0.1", port)
+      ctx = OpenSSL::SSL::SSLContext.new
+      ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
+      ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
+      ssl.sync_close = true
+      begin
+        assert_raise(OpenSSL::SSL::SSLError){ ssl.connect }
+        assert_equal(OpenSSL::X509::V_ERR_SELF_SIGNED_CERT_IN_CHAIN, ssl.verify_result)
+      ensure
+        ssl.close
+      end
+    }
+
+    start_server { |server, port|
+      sock = TCPSocket.new("127.0.0.1", port)
+      ctx = OpenSSL::SSL::SSLContext.new
+      ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
+      ctx.verify_callback = Proc.new do |preverify_ok, store_ctx|
+        store_ctx.error = OpenSSL::X509::V_OK
+        true
+      end
+      ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
+      ssl.sync_close = true
+      begin
+        ssl.connect
+        assert_equal(OpenSSL::X509::V_OK, ssl.verify_result)
+      ensure
+        ssl.close
+      end
+    }
+
+    start_server(ignore_listener_error: true) { |server, port|
+      sock = TCPSocket.new("127.0.0.1", port)
+      ctx = OpenSSL::SSL::SSLContext.new
+      ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
+      ctx.verify_callback = Proc.new do |preverify_ok, store_ctx|
+        store_ctx.error = OpenSSL::X509::V_ERR_APPLICATION_VERIFICATION
+        false
+      end
+      ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
+      ssl.sync_close = true
+      begin
+        assert_raise(OpenSSL::SSL::SSLError){ ssl.connect }
+        assert_equal(OpenSSL::X509::V_ERR_APPLICATION_VERIFICATION, ssl.verify_result)
+      ensure
+        ssl.close
+      end
+    }
+  end
+
+  def test_exception_in_verify_callback_is_ignored
+    start_server(ignore_listener_error: true) { |server, port|
+      sock = TCPSocket.new("127.0.0.1", port)
+      ctx = OpenSSL::SSL::SSLContext.new
+      ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
+      ctx.verify_callback = Proc.new do |preverify_ok, store_ctx|
+        store_ctx.error = OpenSSL::X509::V_OK
+        raise RuntimeError
+      end
+      ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
+      ssl.sync_close = true
+      begin
+        OpenSSL::TestUtils.silent do
+          # SSLError, not RuntimeError
+          assert_raise(OpenSSL::SSL::SSLError) { ssl.connect }
+        end
+        assert_equal(OpenSSL::X509::V_ERR_CERT_REJECTED, ssl.verify_result)
+      ensure
+        ssl.close
+      end
+    }
+  end
+
+  def test_sslctx_set_params
+    ctx = OpenSSL::SSL::SSLContext.new
+    ctx.set_params
+
+    assert_equal OpenSSL::SSL::VERIFY_PEER, ctx.verify_mode
+    ciphers_names = ctx.ciphers.collect{|v, _, _, _| v }
+    assert ciphers_names.all?{|v| /A(EC)?DH/ !~ v }, "anon ciphers are disabled"
+    assert ciphers_names.all?{|v| /(RC4|MD5|EXP|DES)/ !~ v }, "weak ciphers are disabled"
+    assert_equal 0, ctx.options & OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS
+    if defined?(OpenSSL::SSL::OP_NO_COMPRESSION) # >= 1.0.0
+      assert_equal OpenSSL::SSL::OP_NO_COMPRESSION,
+                   ctx.options & OpenSSL::SSL::OP_NO_COMPRESSION
+    end
+  end
+
+  def test_post_connect_check_with_anon_ciphers
+    ctx_proc = -> ctx {
+      ctx.ciphers = "aNULL"
+      ctx.security_level = 0
+    }
+
+    start_server(ctx_proc: ctx_proc) { |server, port|
+      ctx = OpenSSL::SSL::SSLContext.new
+      ctx.ciphers = "aNULL"
+      ctx.security_level = 0
+      server_connect(port, ctx) { |ssl|
+        assert_raise_with_message(OpenSSL::SSL::SSLError, /anonymous cipher suite/i) {
+          ssl.post_connection_check("localhost.localdomain")
+        }
+      }
+    }
+  end
+
+  def test_post_connection_check
+    sslerr = OpenSSL::SSL::SSLError
+
+    start_server { |server, port|
+      server_connect(port) { |ssl|
+        assert_raise(sslerr){ssl.post_connection_check("localhost.localdomain")}
+        assert_raise(sslerr){ssl.post_connection_check("127.0.0.1")}
+        assert(ssl.post_connection_check("localhost"))
+        assert_raise(sslerr){ssl.post_connection_check("foo.example.com")}
+
+        cert = ssl.peer_cert
+        assert(!OpenSSL::SSL.verify_certificate_identity(cert, "localhost.localdomain"))
+        assert(!OpenSSL::SSL.verify_certificate_identity(cert, "127.0.0.1"))
+        assert(OpenSSL::SSL.verify_certificate_identity(cert, "localhost"))
+        assert(!OpenSSL::SSL.verify_certificate_identity(cert, "foo.example.com"))
+      }
+    }
+
+    exts = [
+      ["keyUsage","keyEncipherment,digitalSignature",true],
+      ["subjectAltName","DNS:localhost.localdomain",false],
+      ["subjectAltName","IP:127.0.0.1",false],
+    ]
+    @svr_cert = issue_cert(@svr, @svr_key, 4, exts, @ca_cert, @ca_key)
+    start_server { |server, port|
+      server_connect(port) { |ssl|
+        assert(ssl.post_connection_check("localhost.localdomain"))
+        assert(ssl.post_connection_check("127.0.0.1"))
+        assert_raise(sslerr){ssl.post_connection_check("localhost")}
+        assert_raise(sslerr){ssl.post_connection_check("foo.example.com")}
+
+        cert = ssl.peer_cert
+        assert(OpenSSL::SSL.verify_certificate_identity(cert, "localhost.localdomain"))
+        assert(OpenSSL::SSL.verify_certificate_identity(cert, "127.0.0.1"))
+        assert(!OpenSSL::SSL.verify_certificate_identity(cert, "localhost"))
+        assert(!OpenSSL::SSL.verify_certificate_identity(cert, "foo.example.com"))
+      }
+    }
+
+    exts = [
+      ["keyUsage","keyEncipherment,digitalSignature",true],
+      ["subjectAltName","DNS:*.localdomain",false],
+    ]
+    @svr_cert = issue_cert(@svr, @svr_key, 5, exts, @ca_cert, @ca_key)
+    start_server { |server, port|
+      server_connect(port) { |ssl|
+        assert(ssl.post_connection_check("localhost.localdomain"))
+        assert_raise(sslerr){ssl.post_connection_check("127.0.0.1")}
+        assert_raise(sslerr){ssl.post_connection_check("localhost")}
+        assert_raise(sslerr){ssl.post_connection_check("foo.example.com")}
+        cert = ssl.peer_cert
+        assert(OpenSSL::SSL.verify_certificate_identity(cert, "localhost.localdomain"))
+        assert(!OpenSSL::SSL.verify_certificate_identity(cert, "127.0.0.1"))
+        assert(!OpenSSL::SSL.verify_certificate_identity(cert, "localhost"))
+        assert(!OpenSSL::SSL.verify_certificate_identity(cert, "foo.example.com"))
+      }
+    }
+  end
+
+  def test_verify_certificate_identity
+    [true, false].each do |criticality|
+      cert = create_null_byte_SAN_certificate(criticality)
+      assert_equal(false, OpenSSL::SSL.verify_certificate_identity(cert, 'www.example.com'))
+      assert_equal(true,  OpenSSL::SSL.verify_certificate_identity(cert, "www.example.com\0.evil.com"))
+      assert_equal(false, OpenSSL::SSL.verify_certificate_identity(cert, '192.168.7.255'))
+      assert_equal(true,  OpenSSL::SSL.verify_certificate_identity(cert, '192.168.7.1'))
+      assert_equal(false, OpenSSL::SSL.verify_certificate_identity(cert, '13::17'))
+      assert_equal(true,  OpenSSL::SSL.verify_certificate_identity(cert, '13:0:0:0:0:0:0:17'))
+    end
+  end
+
+  def test_verify_hostname
+    assert_equal(true,  OpenSSL::SSL.verify_hostname("www.example.com", "*.example.com"))
+    assert_equal(false, OpenSSL::SSL.verify_hostname("www.subdomain.example.com", "*.example.com"))
+  end
+
+  def test_verify_wildcard
+    assert_equal(false, OpenSSL::SSL.verify_wildcard("foo", "x*"))
+    assert_equal(true,  OpenSSL::SSL.verify_wildcard("foo", "foo"))
+    assert_equal(true,  OpenSSL::SSL.verify_wildcard("foo", "f*"))
+    assert_equal(true,  OpenSSL::SSL.verify_wildcard("foo", "*"))
+    assert_equal(false, OpenSSL::SSL.verify_wildcard("abc*bcd", "abcd"))
+    assert_equal(false, OpenSSL::SSL.verify_wildcard("xn--qdk4b9b", "x*"))
+    assert_equal(false, OpenSSL::SSL.verify_wildcard("xn--qdk4b9b", "*--qdk4b9b"))
+    assert_equal(true,  OpenSSL::SSL.verify_wildcard("xn--qdk4b9b", "xn--qdk4b9b"))
+  end
+
+  # Comments in this test is excerpted from http://tools.ietf.org/html/rfc6125#page-27
+  def test_post_connection_check_wildcard_san
+    # case-insensitive ASCII comparison
+    # RFC 6125, section 6.4.1
+    #
+    # "..matching of the reference identifier against the presented identifier
+    # is performed by comparing the set of domain name labels using a
+    # case-insensitive ASCII comparison, as clarified by [DNS-CASE] (e.g.,
+    # "WWW.Example.Com" would be lower-cased to "www.example.com" for
+    # comparison purposes)
+    assert_equal(true, OpenSSL::SSL.verify_certificate_identity(
+      create_cert_with_san('DNS:*.example.com'), 'www.example.com'))
+    assert_equal(true, OpenSSL::SSL.verify_certificate_identity(
+      create_cert_with_san('DNS:*.Example.COM'), 'www.example.com'))
+    assert_equal(true, OpenSSL::SSL.verify_certificate_identity(
+      create_cert_with_san('DNS:*.example.com'), 'WWW.Example.COM'))
+    # 1.  The client SHOULD NOT attempt to match a presented identifier in
+    #     which the wildcard character comprises a label other than the
+    #     left-most label (e.g., do not match bar.*.example.net).
+    assert_equal(false, OpenSSL::SSL.verify_certificate_identity(
+      create_cert_with_san('DNS:www.*.com'), 'www.example.com'))
+    # 2.  If the wildcard character is the only character of the left-most
+    #     label in the presented identifier, the client SHOULD NOT compare
+    #     against anything but the left-most label of the reference
+    #     identifier (e.g., *.example.com would match foo.example.com but
+    #     not bar.foo.example.com or example.com).
+    assert_equal(true, OpenSSL::SSL.verify_certificate_identity(
+      create_cert_with_san('DNS:*.example.com'), 'foo.example.com'))
+    assert_equal(false, OpenSSL::SSL.verify_certificate_identity(
+      create_cert_with_san('DNS:*.example.com'), 'bar.foo.example.com'))
+    # 3.  The client MAY match a presented identifier in which the wildcard
+    #     character is not the only character of the label (e.g.,
+    #     baz*.example.net and *baz.example.net and b*z.example.net would
+    #     be taken to match baz1.example.net and foobaz.example.net and
+    #     buzz.example.net, respectively).  ...
+    assert_equal(true, OpenSSL::SSL.verify_certificate_identity(
+      create_cert_with_san('DNS:baz*.example.com'), 'baz1.example.com'))
+    assert_equal(true, OpenSSL::SSL.verify_certificate_identity(
+      create_cert_with_san('DNS:*baz.example.com'), 'foobaz.example.com'))
+    assert_equal(true, OpenSSL::SSL.verify_certificate_identity(
+      create_cert_with_san('DNS:b*z.example.com'), 'buzz.example.com'))
+    # Section 6.4.3 of RFC6125 states that client should NOT match identifier
+    # where wildcard is other than left-most label.
+    #
+    # Also implicitly mentions the wildcard character only in singular form,
+    # and discourages matching against more than one wildcard.
+    #
+    # See RFC 6125, section 7.2, subitem 2.
+    assert_equal(false, OpenSSL::SSL.verify_certificate_identity(
+      create_cert_with_san('DNS:*b*.example.com'), 'abc.example.com'))
+    assert_equal(false, OpenSSL::SSL.verify_certificate_identity(
+      create_cert_with_san('DNS:*b*.example.com'), 'ab.example.com'))
+    assert_equal(false, OpenSSL::SSL.verify_certificate_identity(
+      create_cert_with_san('DNS:*b*.example.com'), 'bc.example.com'))
+    #                                ...  However, the client SHOULD NOT
+    #   attempt to match a presented identifier where the wildcard
+    #   character is embedded within an A-label or U-label [IDNA-DEFS] of
+    #   an internationalized domain name [IDNA-PROTO].
+    assert_equal(true, OpenSSL::SSL.verify_certificate_identity(
+      create_cert_with_san('DNS:xn*.example.com'), 'xn1ca.example.com'))
+    # part of A-label
+    assert_equal(false, OpenSSL::SSL.verify_certificate_identity(
+      create_cert_with_san('DNS:xn--*.example.com'), 'xn--1ca.example.com'))
+    # part of U-label
+    # dNSName in RFC5280 is an IA5String so U-label should NOT be allowed
+    # regardless of wildcard.
+    #
+    # See Section 7.2 of RFC 5280:
+    #   IA5String is limited to the set of ASCII characters.
+    assert_equal(false, OpenSSL::SSL.verify_certificate_identity(
+      create_cert_with_san('DNS:á*.example.com'), 'á1.example.com'))
+  end
+
+  def test_post_connection_check_wildcard_cn
+    assert_equal(true, OpenSSL::SSL.verify_certificate_identity(
+      create_cert_with_name('*.example.com'), 'www.example.com'))
+    assert_equal(true, OpenSSL::SSL.verify_certificate_identity(
+      create_cert_with_name('*.Example.COM'), 'www.example.com'))
+    assert_equal(true, OpenSSL::SSL.verify_certificate_identity(
+      create_cert_with_name('*.example.com'), 'WWW.Example.COM'))
+    assert_equal(false, OpenSSL::SSL.verify_certificate_identity(
+      create_cert_with_name('www.*.com'), 'www.example.com'))
+    assert_equal(true, OpenSSL::SSL.verify_certificate_identity(
+      create_cert_with_name('*.example.com'), 'foo.example.com'))
+    assert_equal(false, OpenSSL::SSL.verify_certificate_identity(
+      create_cert_with_name('*.example.com'), 'bar.foo.example.com'))
+    assert_equal(true, OpenSSL::SSL.verify_certificate_identity(
+      create_cert_with_name('baz*.example.com'), 'baz1.example.com'))
+    assert_equal(true, OpenSSL::SSL.verify_certificate_identity(
+      create_cert_with_name('*baz.example.com'), 'foobaz.example.com'))
+    assert_equal(true, OpenSSL::SSL.verify_certificate_identity(
+      create_cert_with_name('b*z.example.com'), 'buzz.example.com'))
+    # Section 6.4.3 of RFC6125 states that client should NOT match identifier
+    # where wildcard is other than left-most label.
+    #
+    # Also implicitly mentions the wildcard character only in singular form,
+    # and discourages matching against more than one wildcard.
+    #
+    # See RFC 6125, section 7.2, subitem 2.
+    assert_equal(false, OpenSSL::SSL.verify_certificate_identity(
+      create_cert_with_name('*b*.example.com'), 'abc.example.com'))
+    assert_equal(false, OpenSSL::SSL.verify_certificate_identity(
+      create_cert_with_name('*b*.example.com'), 'ab.example.com'))
+    assert_equal(false, OpenSSL::SSL.verify_certificate_identity(
+      create_cert_with_name('*b*.example.com'), 'bc.example.com'))
+    assert_equal(true, OpenSSL::SSL.verify_certificate_identity(
+      create_cert_with_name('xn*.example.com'), 'xn1ca.example.com'))
+    assert_equal(false, OpenSSL::SSL.verify_certificate_identity(
+      create_cert_with_name('xn--*.example.com'), 'xn--1ca.example.com'))
+    # part of U-label
+    # Subject in RFC5280 states case-insensitive ASCII comparison.
+    #
+    # See Section 7.2 of RFC 5280:
+    #   IA5String is limited to the set of ASCII characters.
+    assert_equal(false, OpenSSL::SSL.verify_certificate_identity(
+      create_cert_with_name('á*.example.com'), 'á1.example.com'))
+  end
+
+  def create_cert_with_san(san)
+    ef = OpenSSL::X509::ExtensionFactory.new
+    cert = OpenSSL::X509::Certificate.new
+    cert.subject = OpenSSL::X509::Name.parse("/DC=some/DC=site/CN=Some Site")
+    ext = ef.create_ext('subjectAltName', san)
+    cert.add_extension(ext)
+    cert
+  end
+
+  def create_cert_with_name(name)
+    cert = OpenSSL::X509::Certificate.new
+    cert.subject = OpenSSL::X509::Name.new([['DC', 'some'], ['DC', 'site'], ['CN', name]])
+    cert
+  end
+
+
+  # Create NULL byte SAN certificate
+  def create_null_byte_SAN_certificate(critical = false)
+    ef = OpenSSL::X509::ExtensionFactory.new
+    cert = OpenSSL::X509::Certificate.new
+    cert.subject = OpenSSL::X509::Name.parse "/DC=some/DC=site/CN=Some Site"
+    ext = ef.create_ext('subjectAltName', 'DNS:placeholder,IP:192.168.7.1,IP:13::17', critical)
+    ext_asn1 = OpenSSL::ASN1.decode(ext.to_der)
+    san_list_der = ext_asn1.value.reduce(nil) { |memo,val| val.tag == 4 ? val.value : memo }
+    san_list_asn1 = OpenSSL::ASN1.decode(san_list_der)
+    san_list_asn1.value[0].value = "www.example.com\0.evil.com"
+    pos = critical ? 2 : 1
+    ext_asn1.value[pos].value = san_list_asn1.to_der
+    real_ext = OpenSSL::X509::Extension.new ext_asn1
+    cert.add_extension(real_ext)
+    cert
+  end
+
+  def socketpair
+    if defined? UNIXSocket
+      UNIXSocket.pair
+    else
+      Socket.pair(Socket::AF_INET, Socket::SOCK_STREAM, 0)
+    end
+  end
+
+  def test_tlsext_hostname
+    ctx3 = OpenSSL::SSL::SSLContext.new
+    ctx3.ciphers = "ADH"
+    ctx3.tmp_dh_callback = proc { OpenSSL::TestUtils::TEST_KEY_DH1024 }
+    ctx3.security_level = 0
+    assert_not_predicate ctx3, :frozen?
+
+    ctx_proc = -> ctx {
+      ctx.ciphers = "ALL:!aNULL"
+      ctx.servername_cb = proc { |ssl, servername|
+        case servername
+        when "foo.example.com"
+          ctx3
+        when "bar.example.com"
+          nil
+        else
+          raise "unknown hostname"
+        end
+      }
+    }
+    start_server(ctx_proc: ctx_proc) do |server, port|
+      ctx = OpenSSL::SSL::SSLContext.new
+      ctx.ciphers = "ALL"
+      ctx.security_level = 0
+
+      sock = TCPSocket.new("127.0.0.1", port)
+      begin
+        ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
+        ssl.hostname = "foo.example.com"
+        ssl.connect
+        assert_match (/^ADH-/), ssl.cipher[0], "the context returned by servername_cb is used"
+        assert_predicate ctx3, :frozen?
+      ensure
+        sock.close
+      end
+
+      sock = TCPSocket.new("127.0.0.1", port)
+      begin
+        ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
+        ssl.hostname = "bar.example.com"
+        ssl.connect
+        assert_not_match (/^A(EC)?DH-/), ssl.cipher[0], "the original context is used"
+      ensure
+        sock.close
+      end
+    end
+  end
+
+  def test_servername_cb_raises_an_exception_on_unknown_objects
+    hostname = 'example.org'
+
+    ctx2 = OpenSSL::SSL::SSLContext.new
+    ctx2.ciphers = "aNULL"
+    ctx2.tmp_dh_callback = proc { OpenSSL::TestUtils::TEST_KEY_DH1024 }
+    ctx2.security_level = 0
+    ctx2.servername_cb = lambda { |args| Object.new }
+
+    sock1, sock2 = socketpair
+
+    s2 = OpenSSL::SSL::SSLSocket.new(sock2, ctx2)
+
+    ctx1 = OpenSSL::SSL::SSLContext.new
+    ctx1.ciphers = "aNULL"
+    ctx1.security_level = 0
+
+    s1 = OpenSSL::SSL::SSLSocket.new(sock1, ctx1)
+    s1.hostname = hostname
+    t = Thread.new {
+      assert_raise(OpenSSL::SSL::SSLError) do
+        s1.connect
+      end
+    }
+
+    assert_raise(ArgumentError) do
+      s2.accept
+    end
+
+    assert t.join
+  ensure
+    sock1.close if sock1
+    sock2.close if sock2
+  end
+
+  def test_verify_hostname_on_connect
+    ctx_proc = proc { |ctx|
+      exts = [
+        ["keyUsage", "keyEncipherment,digitalSignature", true],
+        ["subjectAltName", "DNS:a.example.com,DNS:*.b.example.com," \
+                           "DNS:c*.example.com,DNS:d.*.example.com"],
+      ]
+      ctx.cert = issue_cert(@svr, @svr_key, 4, exts, @ca_cert, @ca_key)
+      ctx.key = @svr_key
+    }
+
+    start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |server, port|
+      ctx = OpenSSL::SSL::SSLContext.new
+      ctx.verify_hostname = true
+      ctx.cert_store = OpenSSL::X509::Store.new
+      ctx.cert_store.add_cert(@ca_cert)
+      ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
+
+      [
+        ["a.example.com", true],
+        ["A.Example.Com", true],
+        ["x.example.com", false],
+        ["b.example.com", false],
+        ["x.b.example.com", true],
+        ["cx.example.com", true],
+        ["d.x.example.com", false],
+      ].each do |name, expected_ok|
+        begin
+          sock = TCPSocket.new("127.0.0.1", port)
+          ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
+          ssl.hostname = name
+          if expected_ok
+            assert_nothing_raised { ssl.connect }
+          else
+            assert_handshake_error { ssl.connect }
+          end
+        ensure
+          ssl.close if ssl
+          sock.close if sock
+        end
+      end
+    end
+  end
+
+  def test_multibyte_read_write
+    #German a umlaut
+    auml = [%w{ C3 A4 }.join('')].pack('H*')
+    auml.force_encoding(Encoding::UTF_8)
+
+    [10, 1000, 100000].each {|i|
+      str = nil
+      num_written = nil
+      server_proc = Proc.new {|ctx, ssl|
+        cmp = ssl.read
+        raw_size = cmp.size
+        cmp.force_encoding(Encoding::UTF_8)
+        assert_equal(str, cmp)
+        assert_equal(num_written, raw_size)
+        ssl.close
+      }
+      start_server(server_proc: server_proc) { |server, port|
+        server_connect(port) { |ssl|
+          str = auml * i
+          num_written = ssl.write(str)
+        }
+      }
+    }
+  end
+
+  def test_unset_OP_ALL
+    ctx_proc = Proc.new { |ctx|
+      # If OP_DONT_INSERT_EMPTY_FRAGMENTS is not defined, this test is
+      # redundant because the default options already are equal to OP_ALL.
+      # But it also degrades gracefully, so keep it
+      ctx.options = OpenSSL::SSL::OP_ALL
+    }
+    start_server(ctx_proc: ctx_proc) { |server, port|
+      server_connect(port) { |ssl|
+        ssl.puts('hello')
+        assert_equal("hello\n", ssl.gets)
+      }
+    }
+  end
+
+if OpenSSL::SSL::SSLContext::METHODS.include?(:TLSv1) && OpenSSL::SSL::SSLContext::METHODS.include?(:SSLv3)
+
+  def test_forbid_ssl_v3_for_client
+    ctx_proc = Proc.new { |ctx| ctx.options = OpenSSL::SSL::OP_ALL | OpenSSL::SSL::OP_NO_SSLv3 }
+    start_server_version(:SSLv23, ctx_proc) { |server, port|
+      ctx = OpenSSL::SSL::SSLContext.new
+      ctx.ssl_version = :SSLv3
+      assert_handshake_error { server_connect(port, ctx) }
+    }
+  end
+
+  def test_forbid_ssl_v3_from_server
+    start_server_version(:SSLv3) { |server, port|
+      ctx = OpenSSL::SSL::SSLContext.new
+      ctx.options = OpenSSL::SSL::OP_ALL | OpenSSL::SSL::OP_NO_SSLv3
+      assert_handshake_error { server_connect(port, ctx) }
+    }
+  end
+
+end
+
+if OpenSSL::SSL::SSLContext::METHODS.include? :TLSv1_1
+
+  def test_tls_v1_1
+    start_server_version(:TLSv1_1) { |server, port|
+      server_connect(port) { |ssl| assert_equal("TLSv1.1", ssl.ssl_version) }
+    }
+  end
+
+  def test_forbid_tls_v1_for_client
+    ctx_proc = Proc.new { |ctx| ctx.options = OpenSSL::SSL::OP_ALL | OpenSSL::SSL::OP_NO_TLSv1 }
+    start_server_version(:SSLv23, ctx_proc) { |server, port|
+      ctx = OpenSSL::SSL::SSLContext.new
+      ctx.ssl_version = :TLSv1
+      assert_handshake_error { server_connect(port, ctx) }
+    }
+  end
+
+  def test_forbid_tls_v1_from_server
+    start_server_version(:TLSv1) { |server, port|
+      ctx = OpenSSL::SSL::SSLContext.new
+      ctx.options = OpenSSL::SSL::OP_ALL | OpenSSL::SSL::OP_NO_TLSv1
+      assert_handshake_error { server_connect(port, ctx) }
+    }
+  end
+
+end
+
+if OpenSSL::SSL::SSLContext::METHODS.include? :TLSv1_2
+
+  def test_tls_v1_2
+    start_server_version(:TLSv1_2) { |server, port|
+      ctx = OpenSSL::SSL::SSLContext.new
+      ctx.ssl_version = :TLSv1_2_client
+      server_connect(port, ctx) { |ssl| assert_equal("TLSv1.2", ssl.ssl_version) }
+    }
+  end if OpenSSL::OPENSSL_VERSION_NUMBER > 0x10001000
+
+  def test_forbid_tls_v1_1_for_client
+    ctx_proc = Proc.new { |ctx| ctx.options = OpenSSL::SSL::OP_ALL | OpenSSL::SSL::OP_NO_TLSv1_1 }
+    start_server_version(:SSLv23, ctx_proc) { |server, port|
+      ctx = OpenSSL::SSL::SSLContext.new
+      ctx.ssl_version = :TLSv1_1
+      assert_handshake_error { server_connect(port, ctx) }
+    }
+  end if defined?(OpenSSL::SSL::OP_NO_TLSv1_1)
+
+  def test_forbid_tls_v1_1_from_server
+    start_server_version(:TLSv1_1) { |server, port|
+      ctx = OpenSSL::SSL::SSLContext.new
+      ctx.options = OpenSSL::SSL::OP_ALL | OpenSSL::SSL::OP_NO_TLSv1_1
+      assert_handshake_error { server_connect(port, ctx) }
+    }
+  end if defined?(OpenSSL::SSL::OP_NO_TLSv1_1)
+
+  def test_forbid_tls_v1_2_for_client
+    ctx_proc = Proc.new { |ctx| ctx.options = OpenSSL::SSL::OP_ALL | OpenSSL::SSL::OP_NO_TLSv1_2 }
+    start_server_version(:SSLv23, ctx_proc) { |server, port|
+      ctx = OpenSSL::SSL::SSLContext.new
+      ctx.ssl_version = :TLSv1_2
+      assert_handshake_error { server_connect(port, ctx) }
+    }
+  end if defined?(OpenSSL::SSL::OP_NO_TLSv1_2)
+
+  def test_forbid_tls_v1_2_from_server
+    start_server_version(:TLSv1_2) { |server, port|
+      ctx = OpenSSL::SSL::SSLContext.new
+      ctx.options = OpenSSL::SSL::OP_ALL | OpenSSL::SSL::OP_NO_TLSv1_2
+      assert_handshake_error { server_connect(port, ctx) }
+    }
+  end if defined?(OpenSSL::SSL::OP_NO_TLSv1_2)
+
+end
+
+  def test_renegotiation_cb
+    num_handshakes = 0
+    renegotiation_cb = Proc.new { |ssl| num_handshakes += 1 }
+    ctx_proc = Proc.new { |ctx| ctx.renegotiation_cb = renegotiation_cb }
+    start_server_version(:SSLv23, ctx_proc) { |server, port|
+      server_connect(port) { |ssl|
+        assert_equal(1, num_handshakes)
+      }
+    }
+  end
+
+if OpenSSL::OPENSSL_VERSION_NUMBER >= 0x10002000
+  def test_alpn_protocol_selection_ary
+    advertised = ["http/1.1", "spdy/2"]
+    ctx_proc = Proc.new { |ctx|
+      ctx.alpn_select_cb = -> (protocols) {
+        protocols.first
+      }
+      ctx.alpn_protocols = advertised
+    }
+    start_server_version(:SSLv23, ctx_proc) { |server, port|
+      ctx = OpenSSL::SSL::SSLContext.new
+      ctx.alpn_protocols = advertised
+      server_connect(port, ctx) { |ssl|
+        assert_equal(advertised.first, ssl.alpn_protocol)
+      }
+    }
+  end
+
+  def test_alpn_protocol_selection_cancel
+    sock1, sock2 = socketpair
+
+    ctx1 = OpenSSL::SSL::SSLContext.new
+    ctx1.ciphers = "aNULL"
+    ctx1.security_level = 0
+    ctx1.alpn_select_cb = -> (protocols) { nil }
+    ssl1 = OpenSSL::SSL::SSLSocket.new(sock1, ctx1)
+
+    ctx2 = OpenSSL::SSL::SSLContext.new
+    ctx2.ciphers = "aNULL"
+    ctx2.security_level = 0
+    ctx2.alpn_protocols = ["http/1.1"]
+    ssl2 = OpenSSL::SSL::SSLSocket.new(sock2, ctx2)
+
+    t = Thread.new {
+      ssl2.connect_nonblock(exception: false)
+    }
+    assert_raise_with_message(TypeError, /nil/) { ssl1.accept }
+    t.join
+  ensure
+    sock1&.close
+    sock2&.close
+    ssl1&.close
+    ssl2&.close
+    t&.kill
+    t&.join
+  end
+end
+
+if OpenSSL::OPENSSL_VERSION_NUMBER > 0x10001000 &&
+	OpenSSL::SSL::SSLContext.method_defined?(:npn_select_cb)
+  # NPN may be disabled by OpenSSL configure option
+
+  def test_npn_protocol_selection_ary
+    advertised = ["http/1.1", "spdy/2"]
+    ctx_proc = Proc.new { |ctx| ctx.npn_protocols = advertised }
+    start_server_version(:SSLv23, ctx_proc) { |server, port|
+      selector = lambda { |which|
+        ctx = OpenSSL::SSL::SSLContext.new
+        ctx.npn_select_cb = -> (protocols) { protocols.send(which) }
+        server_connect(port, ctx) { |ssl|
+          assert_equal(advertised.send(which), ssl.npn_protocol)
+        }
+      }
+      selector.call(:first)
+      selector.call(:last)
+    }
+  end
+
+  def test_npn_protocol_selection_enum
+    advertised = Object.new
+    def advertised.each
+      yield "http/1.1"
+      yield "spdy/2"
+    end
+    ctx_proc = Proc.new { |ctx| ctx.npn_protocols = advertised }
+    start_server_version(:SSLv23, ctx_proc) { |server, port|
+      selector = lambda { |selected, which|
+        ctx = OpenSSL::SSL::SSLContext.new
+        ctx.npn_select_cb = -> (protocols) { protocols.to_a.send(which) }
+        server_connect(port, ctx) { |ssl|
+          assert_equal(selected, ssl.npn_protocol)
+        }
+      }
+      selector.call("http/1.1", :first)
+      selector.call("spdy/2", :last)
+    }
+  end
+
+  def test_npn_protocol_selection_cancel
+    ctx_proc = Proc.new { |ctx| ctx.npn_protocols = ["http/1.1"] }
+    start_server_version(:SSLv23, ctx_proc) { |server, port|
+      ctx = OpenSSL::SSL::SSLContext.new
+      ctx.npn_select_cb = -> (protocols) { raise RuntimeError.new }
+      assert_raise(RuntimeError) { server_connect(port, ctx) }
+    }
+  end
+
+  def test_npn_advertised_protocol_too_long
+    ctx_proc = Proc.new { |ctx| ctx.npn_protocols = ["a" * 256] }
+    start_server_version(:SSLv23, ctx_proc) { |server, port|
+      ctx = OpenSSL::SSL::SSLContext.new
+      ctx.npn_select_cb = -> (protocols) { protocols.first }
+      assert_handshake_error { server_connect(port, ctx) }
+    }
+  end
+
+  def test_npn_selected_protocol_too_long
+    ctx_proc = Proc.new { |ctx| ctx.npn_protocols = ["http/1.1"] }
+    start_server_version(:SSLv23, ctx_proc) { |server, port|
+      ctx = OpenSSL::SSL::SSLContext.new
+      ctx.npn_select_cb = -> (protocols) { "a" * 256 }
+      assert_handshake_error { server_connect(port, ctx) }
+    }
+  end
+
+end
+
+  def test_invalid_shutdown_by_gc
+    assert_nothing_raised {
+      start_server { |server, port|
+        10.times {
+          sock = TCPSocket.new("127.0.0.1", port)
+          ssl = OpenSSL::SSL::SSLSocket.new(sock)
+          GC.start
+          ssl.connect
+          sock.close
+        }
+      }
+    }
+  end
+
+  def test_close_after_socket_close
+    start_server { |server, port|
+      sock = TCPSocket.new("127.0.0.1", port)
+      ssl = OpenSSL::SSL::SSLSocket.new(sock)
+      ssl.sync_close = true
+      ssl.connect
+      sock.close
+      assert_nothing_raised do
+        ssl.close
+      end
+    }
+  end
+
+  def test_sync_close_without_connect
+    Socket.open(:INET, :STREAM) {|s|
+      ssl = OpenSSL::SSL::SSLSocket.new(s)
+      ssl.sync_close = true
+      ssl.close
+      assert(s.closed?)
+    }
+  end
+
+  def test_close_and_socket_close_while_connecting
+    # test it doesn't cause a segmentation fault
+    ctx = OpenSSL::SSL::SSLContext.new
+    ctx.ciphers = "aNULL"
+    ctx.tmp_dh_callback = proc { OpenSSL::TestUtils::TEST_KEY_DH1024 }
+    ctx.security_level = 0
+
+    sock1, sock2 = socketpair
+    ssl1 = OpenSSL::SSL::SSLSocket.new(sock1, ctx)
+    ssl2 = OpenSSL::SSL::SSLSocket.new(sock2, ctx)
+
+    t = Thread.new { ssl1.connect }
+    ssl2.accept
+
+    ssl1.close
+    sock1.close
+    t.value rescue nil
+  ensure
+    ssl1.close if ssl1
+    ssl2.close if ssl2
+    sock1.close if sock1
+    sock2.close if sock2
+  end
+
+  def test_get_ephemeral_key
+    return unless OpenSSL::SSL::SSLSocket.method_defined?(:tmp_key)
+    pkey = OpenSSL::PKey
+    ciphers = {
+        'ECDHE-RSA-AES128-SHA' => (pkey::EC if defined?(pkey::EC)),
+        'DHE-RSA-AES128-SHA' => (pkey::DH if defined?(pkey::DH)),
+        'AES128-SHA' => nil
+    }
+    conf_proc = Proc.new { |ctx| ctx.ciphers = 'ALL' }
+    start_server(ctx_proc: conf_proc) do |server, port|
+      ciphers.each do |cipher, ephemeral|
+        ctx = OpenSSL::SSL::SSLContext.new
+        begin
+          ctx.ciphers = cipher
+        rescue OpenSSL::SSL::SSLError => e
+          next if /no cipher match/ =~ e.message
+          raise
+        end
+        server_connect(port, ctx) do |ssl|
+          if ephemeral
+            assert_instance_of(ephemeral, ssl.tmp_key)
+          else
+            assert_nil(ssl.tmp_key)
+          end
+        end
+      end
+    end
+  end
+
+  def test_dh_callback
+    called = false
+    ctx_proc = -> ctx {
+      ctx.ciphers = "DH:!NULL"
+      ctx.tmp_dh_callback = ->(*args) {
+        called = true
+        OpenSSL::TestUtils::TEST_KEY_DH1024
+      }
+    }
+    start_server(ctx_proc: ctx_proc) do |server, port|
+      server_connect(port) { |ssl|
+        assert called, "dh callback should be called"
+        if ssl.respond_to?(:tmp_key)
+          assert_equal OpenSSL::TestUtils::TEST_KEY_DH1024.to_der, ssl.tmp_key.to_der
+        end
+      }
+    end
+  end
+
+  def test_connect_works_when_setting_dh_callback_to_nil
+    ctx_proc = -> ctx {
+      ctx.ciphers = "DH:!NULL" # use DH
+      ctx.tmp_dh_callback = nil
+    }
+    start_server(ctx_proc: ctx_proc) do |server, port|
+      EnvUtil.suppress_warning { # uses default callback
+        assert_nothing_raised {
+          server_connect(port) { }
+        }
+      }
+    end
+  end
+
+  def test_ecdh_callback
+    return unless OpenSSL::SSL::SSLContext.instance_methods.include?(:tmp_ecdh_callback)
+    EnvUtil.suppress_warning do # tmp_ecdh_callback is deprecated (2016-05)
+      begin
+        called = false
+        ctx2 = OpenSSL::SSL::SSLContext.new
+        ctx2.ciphers = "ECDH"
+        # OpenSSL 1.1.0 doesn't have tmp_ecdh_callback so this shouldn't be required
+        ctx2.security_level = 0
+        ctx2.tmp_ecdh_callback = ->(*args) {
+          called = true
+          OpenSSL::PKey::EC.new "prime256v1"
+        }
+
+        sock1, sock2 = socketpair
+
+        s2 = OpenSSL::SSL::SSLSocket.new(sock2, ctx2)
+        ctx1 = OpenSSL::SSL::SSLContext.new
+        ctx1.ciphers = "ECDH"
+        ctx1.security_level = 0
+
+        s1 = OpenSSL::SSL::SSLSocket.new(sock1, ctx1)
+        th = Thread.new do
+          s1.connect
+        end
+
+        s2.accept
+        assert called, 'ecdh callback should be called'
+      rescue OpenSSL::SSL::SSLError => e
+        if e.message =~ /no cipher match/
+          pend "ECDH cipher not supported."
+        else
+          raise e
+        end
+      ensure
+        th.join if th
+        s1.close if s1
+        s2.close if s2
+        sock1.close if sock1
+        sock2.close if sock2
+      end
+    end
+  end
+
+  def test_ecdh_curves
+    ctx_proc = -> ctx {
+      begin
+        ctx.ciphers = "ECDH:!NULL"
+      rescue OpenSSL::SSL::SSLError
+        pend "ECDH is not enabled in this OpenSSL" if $!.message =~ /no cipher match/
+        raise
+      end
+      ctx.ecdh_curves = "P-384:P-521"
+    }
+    start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |server, port|
+      ctx = OpenSSL::SSL::SSLContext.new
+      ctx.ecdh_curves = "P-256:P-384" # disable P-521 for OpenSSL >= 1.0.2
+
+      server_connect(port, ctx) { |ssl|
+        assert ssl.cipher[0].start_with?("ECDH"), "ECDH should be used"
+        if ssl.respond_to?(:tmp_key)
+          assert_equal "secp384r1", ssl.tmp_key.group.curve_name
+        end
+      }
+
+      if OpenSSL::OPENSSL_VERSION_NUMBER >= 0x10002000 &&
+          !OpenSSL::OPENSSL_VERSION.include?("LibreSSL")
+        ctx = OpenSSL::SSL::SSLContext.new
+        ctx.ecdh_curves = "P-256"
+
+        assert_raise(OpenSSL::SSL::SSLError) {
+          server_connect(port, ctx) { }
+        }
+
+        ctx = OpenSSL::SSL::SSLContext.new
+        ctx.ecdh_curves = "P-521:P-384"
+
+        server_connect(port, ctx) { |ssl|
+          assert_equal "secp521r1", ssl.tmp_key.group.curve_name
+        }
+      end
+    end
+  end
+
+  def test_security_level
+    ctx = OpenSSL::SSL::SSLContext.new
+    begin
+      ctx.security_level = 1
+    rescue NotImplementedError
+      assert_equal(0, ctx.security_level)
+      return
+    end
+    assert_equal(1, ctx.security_level)
+    # assert_raise(OpenSSL::SSL::SSLError) { ctx.key = OpenSSL::TestUtils::TEST_KEY_DSA512 }
+    # ctx.key = OpenSSL::TestUtils::TEST_KEY_RSA1024
+    # ctx.security_level = 2
+    # assert_raise(OpenSSL::SSL::SSLError) { ctx.key = OpenSSL::TestUtils::TEST_KEY_RSA1024 }
+    pend "FIXME: SSLContext#key= currently does not raise because SSL_CTX_use_certificate() is delayed"
+  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
+
+  def test_freeze_calls_setup
+    bug = "[ruby/openssl#85]"
+    start_server(ignore_listener_error: true) { |server, port|
+      ctx = OpenSSL::SSL::SSLContext.new
+      ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
+      ctx.freeze
+      assert_raise(OpenSSL::SSL::SSLError, bug) {
+        server_connect(port, ctx)
+      }
+    }
+  end
+
+  private
+
+  def start_server_version(version, ctx_proc = nil,
+                           server_proc = method(:readwrite_loop), &blk)
+    ctx_wrap = Proc.new { |ctx|
+      ctx.ssl_version = version
+      ctx_proc.call(ctx) if ctx_proc
+    }
+    start_server(
+      ctx_proc: ctx_wrap,
+      server_proc: server_proc,
+      ignore_listener_error: true,
+      &blk
+    )
+  end
+
+  def server_connect(port, ctx=nil)
+    sock = TCPSocket.new("127.0.0.1", port)
+    ssl = ctx ? OpenSSL::SSL::SSLSocket.new(sock, ctx) : OpenSSL::SSL::SSLSocket.new(sock)
+    ssl.sync_close = true
+    ssl.connect
+    yield ssl if block_given?
+  ensure
+    if ssl
+      ssl.close
+    elsif sock
+      sock.close
+    end
+  end
+
+  def assert_handshake_error
+    # different OpenSSL versions react differently when facing a SSL/TLS version
+    # that has been marked as forbidden, therefore either of these may be raised
+    assert_raise(OpenSSL::SSL::SSLError, Errno::ECONNRESET) {
+      yield
+    }
+  end
+end
+
+end
diff --git a/test/test_ssl_session.rb b/test/test_ssl_session.rb
new file mode 100644
index 0000000..b2643ed
--- /dev/null
+++ b/test/test_ssl_session.rb
@@ -0,0 +1,380 @@
+# frozen_string_literal: false
+require_relative "utils"
+
+if defined?(OpenSSL::TestUtils)
+
+class OpenSSL::TestSSLSession < OpenSSL::SSLTestCase
+  def test_session_equals
+    session = OpenSSL::SSL::Session.new <<-SESSION
+-----BEGIN SSL SESSION PARAMETERS-----
+MIIDFgIBAQICAwEEAgA5BCCY3pW6iTkPoD5SENuztz/gZjhvey6XnHbsxd22k0Ol
+dgQw8uaN3hCRnlhoIKPWInCFzrp/tQsDRFs9jDjc9pwpy/oKHmJdQQMQA1g8FYnO
+gpdVoQYCBE52ikKiBAICASyjggKOMIICijCCAXKgAwIBAgIBAjANBgkqhkiG9w0B
+AQUFADA9MRMwEQYKCZImiZPyLGQBGRYDb3JnMRkwFwYKCZImiZPyLGQBGRYJcnVi
+eS1sYW5nMQswCQYDVQQDDAJDQTAeFw0xMTA5MTkwMDE4MTBaFw0xMTA5MTkwMDQ4
+MTBaMEQxEzARBgoJkiaJk/IsZAEZFgNvcmcxGTAXBgoJkiaJk/IsZAEZFglydWJ5
+LWxhbmcxEjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
+gYkCgYEAy8LEsNRApz7U/j5DoB4XBgO9Z8Atv5y/OVQRp0ag8Tqo1YewsWijxEWB
+7JOATwpBN267U4T1nPZIxxEEO7n/WNa2ws9JWsjah8ssEBFSxZqdXKSLf0N4Hi7/
+GQ/aYoaMCiQ8jA4jegK2FJmXM71uPe+jFN/peeBOpRfyXxRFOYcCAwEAAaMSMBAw
+DgYDVR0PAQH/BAQDAgWgMA0GCSqGSIb3DQEBBQUAA4IBAQARC7GP7InX1t7VEXz2
+I8RI57S0/HSJL4fDIYP3zFpitHX1PZeo+7XuzMilvPjjBo/ky9Jzo8TYiY+N+JEz
+mY/A/zPA4ZsJ7KYj6/FEdIc/vRlS0CvsbClbNjw1jl/PoB2FLr2b3uuBcZEsyZeP
+yq154ijq37Ajf8K5Mi5FgshoP41BPtRPj+VVf61rv1IcEnNWdDCS6DR4XsaNC+zt
+G6AqCqkytIXWRuDw6n6vYLF3A/tn2sldLo7/scY0PMDNbo63O/LTxkDHmPhSkD68
+8m9SsMeTR+RCiDEZWFPVcAH/8mDfi+5k8uN3qS+gOU/PPrmHGgl5ykiSFgqs4v61
+tddwpBAEDjcwMzA5NTYzMTU1MzAwpQMCARM=
+-----END SSL SESSION PARAMETERS-----
+    SESSION
+
+    start_server(ignore_listener_error: true) { |_, port|
+      ctx = OpenSSL::SSL::SSLContext.new
+      ctx.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT
+      ctx.session_id_context = self.object_id.to_s
+
+      sock = TCPSocket.new '127.0.0.1', port
+      begin
+        ssl = OpenSSL::SSL::SSLSocket.new sock, ctx
+        ssl.session = session
+
+        assert_equal session, ssl.session
+      ensure
+        sock.close
+      end
+    }
+  end
+
+  def test_session
+    Timeout.timeout(5) do
+      start_server do |server, port|
+        sock = TCPSocket.new("127.0.0.1", port)
+        ctx = OpenSSL::SSL::SSLContext.new("TLSv1")
+        ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
+        ssl.sync_close = true
+        ssl.connect
+        session = ssl.session
+        assert(session == OpenSSL::SSL::Session.new(session.to_pem))
+        assert(session == OpenSSL::SSL::Session.new(ssl))
+        session.timeout = 5
+        assert_equal(5, session.timeout)
+        assert_not_nil(session.time)
+        # SSL_SESSION_time keeps long value so we can't keep nsec fragment.
+        session.time = t1 = Time.now.to_i
+        assert_equal(Time.at(t1), session.time)
+        assert_not_nil(session.id)
+        pem = session.to_pem
+        assert_match(/\A-----BEGIN SSL SESSION PARAMETERS-----/, pem)
+        assert_match(/-----END SSL SESSION PARAMETERS-----\Z/, pem)
+        pem.gsub!(/-----(BEGIN|END) SSL SESSION PARAMETERS-----/, '').gsub!(/[\r\n]+/m, '')
+        assert_equal(session.to_der, pem.unpack('m*')[0])
+        assert_not_nil(session.to_text)
+        ssl.close
+      end
+    end
+  end
+
+  DUMMY_SESSION = <<__EOS__
+-----BEGIN SSL SESSION PARAMETERS-----
+MIIDzQIBAQICAwEEAgA5BCAF219w9ZEV8dNA60cpEGOI34hJtIFbf3bkfzSgMyad
+MQQwyGLbkCxE4OiMLdKKem+pyh8V7ifoP7tCxhdmwoDlJxI1v6nVCjai+FGYuncy
+NNSWoQYCBE4DDWuiAwIBCqOCAo4wggKKMIIBcqADAgECAgECMA0GCSqGSIb3DQEB
+BQUAMD0xEzARBgoJkiaJk/IsZAEZFgNvcmcxGTAXBgoJkiaJk/IsZAEZFglydWJ5
+LWxhbmcxCzAJBgNVBAMMAkNBMB4XDTExMDYyMzA5NTQ1MVoXDTExMDYyMzEwMjQ1
+MVowRDETMBEGCgmSJomT8ixkARkWA29yZzEZMBcGCgmSJomT8ixkARkWCXJ1Ynkt
+bGFuZzESMBAGA1UEAwwJbG9jYWxob3N0MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB
+iQKBgQDLwsSw1ECnPtT+PkOgHhcGA71nwC2/nL85VBGnRqDxOqjVh7CxaKPERYHs
+k4BPCkE3brtThPWc9kjHEQQ7uf9Y1rbCz0layNqHyywQEVLFmp1cpIt/Q3geLv8Z
+D9pihowKJDyMDiN6ArYUmZczvW4976MU3+l54E6lF/JfFEU5hwIDAQABoxIwEDAO
+BgNVHQ8BAf8EBAMCBaAwDQYJKoZIhvcNAQEFBQADggEBACj5WhoZ/ODVeHpwgq1d
+8fW/13ICRYHYpv6dzlWihyqclGxbKMlMnaVCPz+4JaVtMz3QB748KJQgL3Llg3R1
+ek+f+n1MBCMfFFsQXJ2gtLB84zD6UCz8aaCWN5/czJCd7xMz7fRLy3TOIW5boXAU
+zIa8EODk+477K1uznHm286ab0Clv+9d304hwmBZgkzLg6+31Of6d6s0E0rwLGiS2
+sOWYg34Y3r4j8BS9Ak4jzpoLY6cJ0QAKCOJCgmjGr4XHpyXMLbicp3ga1uSbwtVO
+gF/gTfpLhJC+y0EQ5x3Ftl88Cq7ZJuLBDMo/TLIfReJMQu/HlrTT7+LwtneSWGmr
+KkSkAgQApQMCAROqgcMEgcAuDkAVfj6QAJMz9yqTzW5wPFyty7CxUEcwKjUqj5UP
+/Yvky1EkRuM/eQfN7ucY+MUvMqv+R8ZSkHPsnjkBN5ChvZXjrUSZKFVjR4eFVz2V
+jismLEJvIFhQh6pqTroRrOjMfTaM5Lwoytr2FTGobN9rnjIRsXeFQW1HLFbXn7Dh
+8uaQkMwIVVSGRB8T7t6z6WIdWruOjCZ6G5ASI5XoqAHwGezhLodZuvJEfsVyCF9y
+j+RBGfCFrrQbBdnkFI/ztgM=
+-----END SSL SESSION PARAMETERS-----
+__EOS__
+
+  DUMMY_SESSION_NO_EXT = <<-__EOS__
+-----BEGIN SSL SESSION PARAMETERS-----
+MIIDCAIBAQICAwAEAgA5BCDyAW7rcpzMjDSosH+Tv6sukymeqgq3xQVVMez628A+
+lAQw9TrKzrIqlHEh6ltuQaqv/Aq83AmaAlogYktZgXAjOGnhX7ifJDNLMuCfQq53
+hPAaoQYCBE4iDeeiBAICASyjggKOMIICijCCAXKgAwIBAgIBAjANBgkqhkiG9w0B
+AQUFADA9MRMwEQYKCZImiZPyLGQBGRYDb3JnMRkwFwYKCZImiZPyLGQBGRYJcnVi
+eS1sYW5nMQswCQYDVQQDDAJDQTAeFw0xMTA3MTYyMjE3MTFaFw0xMTA3MTYyMjQ3
+MTFaMEQxEzARBgoJkiaJk/IsZAEZFgNvcmcxGTAXBgoJkiaJk/IsZAEZFglydWJ5
+LWxhbmcxEjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
+gYkCgYEAy8LEsNRApz7U/j5DoB4XBgO9Z8Atv5y/OVQRp0ag8Tqo1YewsWijxEWB
+7JOATwpBN267U4T1nPZIxxEEO7n/WNa2ws9JWsjah8ssEBFSxZqdXKSLf0N4Hi7/
+GQ/aYoaMCiQ8jA4jegK2FJmXM71uPe+jFN/peeBOpRfyXxRFOYcCAwEAAaMSMBAw
+DgYDVR0PAQH/BAQDAgWgMA0GCSqGSIb3DQEBBQUAA4IBAQA3TRzABRG3kz8jEEYr
+tDQqXgsxwTsLhTT5d1yF0D8uFw+y15hJAJnh6GJHjqhWBrF4zNoTApFo+4iIL6g3
+q9C3mUsxIVAHx41DwZBh/FI7J4FqlAoGOguu7892CNVY3ZZjc3AXMTdKjcNoWPzz
+FCdj5fNT24JMMe+ZdGZK97ChahJsdn/6B3j6ze9NK9mfYEbiJhejGTPLOFVHJCGR
+KYYZ3ZcKhLDr9ql4d7cCo1gBtemrmFQGPui7GttNEqmXqUKvV8mYoa8farf5i7T4
+L6a/gp2cVZTaDIS1HjbJsA/Ag7AajZqiN6LfqShNUVsrMZ+5CoV8EkBDTZPJ9MSr
+a3EqpAIEAKUDAgET
+-----END SSL SESSION PARAMETERS-----
+__EOS__
+
+
+  def test_session_time
+    sess = OpenSSL::SSL::Session.new(DUMMY_SESSION_NO_EXT)
+    sess.time = (now = Time.now)
+    assert_equal(now.to_i, sess.time.to_i)
+    sess.time = 1
+    assert_equal(1, sess.time.to_i)
+    sess.time = 1.2345
+    assert_equal(1, sess.time.to_i)
+    # Can OpenSSL handle t>2038y correctly? Version?
+    sess.time = 2**31 - 1
+    assert_equal(2**31 - 1, sess.time.to_i)
+  end
+
+  def test_session_timeout
+    sess = OpenSSL::SSL::Session.new(DUMMY_SESSION_NO_EXT)
+    assert_raise(TypeError) do
+      sess.timeout = Time.now
+    end
+    sess.timeout = 1
+    assert_equal(1, sess.timeout.to_i)
+    sess.timeout = 1.2345
+    assert_equal(1, sess.timeout.to_i)
+    sess.timeout = 2**31 - 1
+    assert_equal(2**31 - 1, sess.timeout.to_i)
+  end
+
+  def test_session_exts_read
+    assert(OpenSSL::SSL::Session.new(DUMMY_SESSION))
+  end if OpenSSL::OPENSSL_VERSION_NUMBER >= 0x009080bf
+
+  def test_client_session
+    last_session = nil
+    start_server do |server, port|
+      2.times do
+        sock = TCPSocket.new("127.0.0.1", port)
+        # Debian's openssl 0.9.8g-13 failed at assert(ssl.session_reused?),
+        # when use default SSLContext. [ruby-dev:36167]
+        ctx = OpenSSL::SSL::SSLContext.new("TLSv1")
+        ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
+        ssl.sync_close = true
+        ssl.session = last_session if last_session
+        ssl.connect
+
+        session = ssl.session
+        if last_session
+          assert(ssl.session_reused?)
+          assert_equal(session.id, last_session.id)
+          assert_equal(session.to_pem, last_session.to_pem)
+          assert_equal(session.to_der, last_session.to_der)
+          # Older version of OpenSSL may not be consistent.  Look up which versions later.
+          assert_equal(session.to_text, last_session.to_text)
+        else
+          assert(!ssl.session_reused?)
+        end
+        last_session = session
+
+        str = "x" * 100 + "\n"
+        ssl.puts(str)
+        assert_equal(str, ssl.gets)
+
+        ssl.close
+      end
+    end
+  end
+
+  def test_server_session
+    connections = 0
+    saved_session = nil
+
+    ctx_proc = Proc.new do |ctx, ssl|
+# add test for session callbacks here
+    end
+
+    server_proc = Proc.new do |ctx, ssl|
+      session = ssl.session
+      stats = ctx.session_cache_stats
+
+      case connections
+      when 0
+        assert_equal(stats[:cache_num], 1)
+        assert_equal(stats[:cache_hits], 0)
+        assert_equal(stats[:cache_misses], 0)
+        assert(!ssl.session_reused?)
+      when 1
+        assert_equal(stats[:cache_num], 1)
+        assert_equal(stats[:cache_hits], 1)
+        assert_equal(stats[:cache_misses], 0)
+        assert(ssl.session_reused?)
+        ctx.session_remove(session)
+        saved_session = session.to_der
+      when 2
+        assert_equal(stats[:cache_num], 1)
+        assert_equal(stats[:cache_hits], 1)
+        assert_equal(stats[:cache_misses], 1)
+        assert(!ssl.session_reused?)
+        ctx.session_add(OpenSSL::SSL::Session.new(saved_session))
+      when 3
+        assert_equal(stats[:cache_num], 2)
+        assert_equal(stats[:cache_hits], 2)
+        assert_equal(stats[:cache_misses], 1)
+        assert(ssl.session_reused?)
+        ctx.flush_sessions(Time.now + 10000)
+      when 4
+        assert_equal(stats[:cache_num], 1)
+        assert_equal(stats[:cache_hits], 2)
+        assert_equal(stats[:cache_misses], 2)
+        assert(!ssl.session_reused?)
+        ctx.session_add(OpenSSL::SSL::Session.new(saved_session))
+      end
+      connections += 1
+
+      readwrite_loop(ctx, ssl)
+    end
+
+    first_session = nil
+    start_server(ctx_proc: ctx_proc, server_proc: server_proc) do |server, port|
+      10.times do |i|
+        sock = TCPSocket.new("127.0.0.1", port)
+        ctx = OpenSSL::SSL::SSLContext.new
+        # disable RFC4507 support
+        ctx.options = OpenSSL::SSL::OP_NO_TICKET
+        ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
+        ssl.sync_close = true
+        ssl.session = first_session if first_session
+        ssl.connect
+
+        session = ssl.session
+        if first_session
+          case i
+          when 1; assert(ssl.session_reused?)
+          when 2; assert(!ssl.session_reused?)
+          when 3; assert(ssl.session_reused?)
+          when 4; assert(!ssl.session_reused?)
+          when 5..10; assert(ssl.session_reused?)
+          end
+        end
+        first_session ||= session
+
+        str = "x" * 100 + "\n"
+        ssl.puts(str)
+        assert_equal(str, ssl.gets)
+
+        ssl.close
+      end
+    end
+  end
+
+  def test_ctx_client_session_cb
+    called = {}
+    ctx = OpenSSL::SSL::SSLContext.new
+    ctx.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT
+
+    ctx.session_new_cb = lambda { |ary|
+      sock, sess = ary
+      called[:new] = [sock, sess]
+    }
+
+    ctx.session_remove_cb = lambda { |ary|
+      ctx, sess = ary
+      called[:remove] = [ctx, sess]
+      # any resulting value is OK (ignored)
+    }
+
+    start_server do |server, port|
+      sock = TCPSocket.new("127.0.0.1", port)
+      begin
+        ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
+        ssl.sync_close = true
+        ssl.connect
+        assert_equal(1, ctx.session_cache_stats[:cache_num])
+        assert_equal(1, ctx.session_cache_stats[:connect_good])
+        assert_equal([ssl, ssl.session], called[:new])
+        assert(ctx.session_remove(ssl.session))
+        assert(!ctx.session_remove(ssl.session))
+        assert_equal([ctx, ssl.session], called[:remove])
+        ssl.close
+      ensure
+        sock.close if !sock.closed?
+      end
+    end
+  end
+
+  def test_ctx_server_session_cb
+    called = {}
+
+    ctx_proc = Proc.new { |ctx, ssl|
+      ctx.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_SERVER
+      ctx.options = OpenSSL::SSL::OP_NO_TICKET
+      last_server_session = nil
+
+      # get_cb is called whenever a client proposed to resume a session but
+      # the session could not be found in the internal session cache.
+      ctx.session_get_cb = lambda { |ary|
+        sess, data = ary
+        if last_server_session
+          called[:get2] = [sess, data]
+          last_server_session
+        else
+          called[:get1] = [sess, data]
+          last_server_session = sess
+          nil
+        end
+      }
+
+      ctx.session_new_cb = lambda { |ary|
+        sock, sess = ary
+        called[:new] = [sock, sess]
+        # SSL server doesn't cache sessions so get_cb is called next time.
+        ctx.session_remove(sess)
+      }
+
+      ctx.session_remove_cb = lambda { |ary|
+        ctx, sess = ary
+        called[:remove] = [ctx, sess]
+      }
+    }
+
+    server_proc = Proc.new { |c, ssl|
+      ssl.session
+      c.session_cache_stats
+      readwrite_loop(c, ssl)
+    }
+    start_server(ctx_proc: ctx_proc, server_proc: server_proc) do |server, port|
+      last_client_session = nil
+      3.times do
+        sock = TCPSocket.new("127.0.0.1", port)
+        begin
+          ssl = OpenSSL::SSL::SSLSocket.new(sock, OpenSSL::SSL::SSLContext.new())
+          ssl.sync_close = true
+          ssl.session = last_client_session if last_client_session
+          ssl.connect
+          last_client_session = ssl.session
+          ssl.close
+          Timeout.timeout(5) do
+            Thread.pass until called.key?(:new)
+            assert(called.delete(:new))
+            Thread.pass until called.key?(:remove)
+            assert(called.delete(:remove))
+          end
+        ensure
+          sock.close if !sock.closed?
+        end
+      end
+    end
+    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/test_x509attr.rb b/test/test_x509attr.rb
new file mode 100644
index 0000000..d7473f1
--- /dev/null
+++ b/test/test_x509attr.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: false
+require_relative "utils"
+
+if defined?(OpenSSL::TestUtils)
+
+class OpenSSL::TestX509Attribute < OpenSSL::TestCase
+  def test_new
+    ef = OpenSSL::X509::ExtensionFactory.new
+    val = OpenSSL::ASN1::Set.new([OpenSSL::ASN1::Sequence.new([
+      ef.create_extension("keyUsage", "keyCertSign", true)
+    ])])
+    attr = OpenSSL::X509::Attribute.new("extReq", val)
+    assert_equal("extReq", attr.oid)
+    assert_equal(val.to_der, attr.value.to_der)
+
+    attr = OpenSSL::X509::Attribute.new("1.2.840.113549.1.9.14", val)
+    assert_equal("extReq", attr.oid)
+  end
+
+  def test_from_der
+    # oid: challengePassword, values: Set[UTF8String<"abc123">]
+    test_der = "\x30\x15\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x09\x07\x31\x08" \
+      "\x0c\x06\x61\x62\x63\x31\x32\x33".b
+    attr = OpenSSL::X509::Attribute.new(test_der)
+    assert_equal(test_der, attr.to_der)
+    assert_equal("challengePassword", attr.oid)
+    assert_equal("abc123", attr.value.value[0].value)
+  end
+
+  def test_to_der
+    ef = OpenSSL::X509::ExtensionFactory.new
+    val = OpenSSL::ASN1::Set.new([OpenSSL::ASN1::Sequence.new([
+      ef.create_extension("keyUsage", "keyCertSign", true)
+    ])])
+    attr = OpenSSL::X509::Attribute.new("extReq", val)
+    expected = OpenSSL::ASN1::Sequence.new([
+      OpenSSL::ASN1::ObjectId.new("extReq"),
+      val
+    ])
+    assert_equal(expected.to_der, attr.to_der)
+  end
+
+  def test_invalid_value
+    # should not change the original value
+    test_der = "\x30\x15\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x09\x07\x31\x08" \
+      "\x0c\x06\x61\x62\x63\x31\x32\x33".b
+    attr = OpenSSL::X509::Attribute.new(test_der)
+    assert_raise(TypeError) {
+      attr.value = "1234"
+    }
+    assert_equal(test_der, attr.to_der)
+    assert_raise(OpenSSL::X509::AttributeError) {
+      attr.oid = "abc123"
+    }
+    assert_equal(test_der, attr.to_der)
+  end
+
+  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/test_x509cert.rb b/test/test_x509cert.rb
new file mode 100644
index 0000000..0cfe440
--- /dev/null
+++ b/test/test_x509cert.rb
@@ -0,0 +1,190 @@
+# frozen_string_literal: false
+require_relative "utils"
+
+if defined?(OpenSSL::TestUtils)
+
+class OpenSSL::TestX509Certificate < OpenSSL::TestCase
+  def setup
+    super
+    @rsa1024 = OpenSSL::TestUtils::TEST_KEY_RSA1024
+    @rsa2048 = OpenSSL::TestUtils::TEST_KEY_RSA2048
+    @dsa256  = OpenSSL::TestUtils::TEST_KEY_DSA256
+    @dsa512  = OpenSSL::TestUtils::TEST_KEY_DSA512
+    @ca = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=CA")
+    @ee1 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=EE1")
+  end
+
+  def issue_cert(*args)
+    OpenSSL::TestUtils.issue_cert(*args)
+  end
+
+  def test_serial
+    [1, 2**32, 2**100].each{|s|
+      cert = issue_cert(@ca, @rsa2048, s, [], nil, nil)
+      assert_equal(s, cert.serial)
+      cert = OpenSSL::X509::Certificate.new(cert.to_der)
+      assert_equal(s, cert.serial)
+    }
+  end
+
+  def test_public_key
+    exts = [
+      ["basicConstraints","CA:TRUE",true],
+      ["subjectKeyIdentifier","hash",false],
+      ["authorityKeyIdentifier","keyid:always",false],
+    ]
+
+    sha1 = OpenSSL::Digest::SHA1.new
+    dsa_digest = OpenSSL::TestUtils::DSA_SIGNATURE_DIGEST.new
+
+    [
+      [@rsa1024, sha1], [@rsa2048, sha1], [@dsa256, dsa_digest], [@dsa512, dsa_digest]
+    ].each{|pk, digest|
+      cert = issue_cert(@ca, pk, 1, exts, nil, nil, digest: digest)
+      assert_equal(cert.extensions.sort_by(&:to_s)[2].value,
+                   OpenSSL::TestUtils.get_subject_key_id(cert))
+      cert = OpenSSL::X509::Certificate.new(cert.to_der)
+      assert_equal(cert.extensions.sort_by(&:to_s)[2].value,
+                   OpenSSL::TestUtils.get_subject_key_id(cert))
+    }
+  end
+
+  def test_validity
+    now = Time.at(Time.now.to_i + 0.9)
+    cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil,
+                      not_before: now, not_after: now+3600)
+    assert_equal(Time.at(now.to_i), cert.not_before)
+    assert_equal(Time.at(now.to_i+3600), cert.not_after)
+
+    now = Time.at(now.to_i)
+    cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil,
+                      not_before: now, not_after: now+3600)
+    assert_equal(now.getutc, cert.not_before)
+    assert_equal((now+3600).getutc, cert.not_after)
+
+    now = Time.at(0)
+    cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil,
+                      not_before: now, not_after: now)
+    assert_equal(now.getutc, cert.not_before)
+    assert_equal(now.getutc, cert.not_after)
+
+    now = Time.at(0x7fffffff)
+    cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil,
+                      not_before: now, not_after: now)
+    assert_equal(now.getutc, cert.not_before)
+    assert_equal(now.getutc, cert.not_after)
+  end
+
+  def test_extension
+    ca_exts = [
+      ["basicConstraints","CA:TRUE",true],
+      ["keyUsage","keyCertSign, cRLSign",true],
+      ["subjectKeyIdentifier","hash",false],
+      ["authorityKeyIdentifier","keyid:always",false],
+    ]
+    ca_cert = issue_cert(@ca, @rsa2048, 1, ca_exts, nil, nil)
+    ca_cert.extensions.each_with_index{|ext, i|
+      assert_equal(ca_exts[i].first, ext.oid)
+      assert_equal(ca_exts[i].last, ext.critical?)
+    }
+
+    ee1_exts = [
+      ["keyUsage","Non Repudiation, Digital Signature, Key Encipherment",true],
+      ["subjectKeyIdentifier","hash",false],
+      ["authorityKeyIdentifier","keyid:always",false],
+      ["extendedKeyUsage","clientAuth, emailProtection, codeSigning",false],
+      ["subjectAltName","email:ee1 at ruby-lang.org",false],
+    ]
+    ee1_cert = issue_cert(@ee1, @rsa1024, 2, ee1_exts, ca_cert, @rsa2048)
+    assert_equal(ca_cert.subject.to_der, ee1_cert.issuer.to_der)
+    ee1_cert.extensions.each_with_index{|ext, i|
+      assert_equal(ee1_exts[i].first, ext.oid)
+      assert_equal(ee1_exts[i].last, ext.critical?)
+    }
+  end
+
+  def test_sign_and_verify_rsa_sha1
+    cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil, digest: "sha1")
+    assert_equal(false, cert.verify(@rsa1024))
+    assert_equal(true,  cert.verify(@rsa2048))
+    assert_equal(false, certificate_error_returns_false { cert.verify(@dsa256) })
+    assert_equal(false, certificate_error_returns_false { cert.verify(@dsa512) })
+    cert.serial = 2
+    assert_equal(false, cert.verify(@rsa2048))
+  end
+
+  def test_sign_and_verify_rsa_md5
+    cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil, digest: "md5")
+    assert_equal(false, cert.verify(@rsa1024))
+    assert_equal(true, cert.verify(@rsa2048))
+
+    assert_equal(false, certificate_error_returns_false { cert.verify(@dsa256) })
+    assert_equal(false, certificate_error_returns_false { cert.verify(@dsa512) })
+    cert.subject = @ee1
+    assert_equal(false, cert.verify(@rsa2048))
+  rescue OpenSSL::X509::CertificateError # RHEL7 disables MD5
+  end
+
+  def test_sign_and_verify_dsa
+    cert = issue_cert(@ca, @dsa512, 1, [], nil, nil)
+    assert_equal(false, certificate_error_returns_false { cert.verify(@rsa1024) })
+    assert_equal(false, certificate_error_returns_false { cert.verify(@rsa2048) })
+    assert_equal(false, cert.verify(@dsa256))
+    assert_equal(true,  cert.verify(@dsa512))
+    cert.not_after = Time.now
+    assert_equal(false, cert.verify(@dsa512))
+  end
+
+  def test_sign_and_verify_rsa_dss1
+    cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil, digest: OpenSSL::Digest::DSS1.new)
+    assert_equal(false, cert.verify(@rsa1024))
+    assert_equal(true, cert.verify(@rsa2048))
+    assert_equal(false, certificate_error_returns_false { cert.verify(@dsa256) })
+    assert_equal(false, certificate_error_returns_false { cert.verify(@dsa512) })
+    cert.subject = @ee1
+    assert_equal(false, cert.verify(@rsa2048))
+  rescue OpenSSL::X509::CertificateError
+  end if defined?(OpenSSL::Digest::DSS1)
+
+  def test_sign_and_verify_dsa_md5
+    assert_raise(OpenSSL::X509::CertificateError){
+      issue_cert(@ca, @dsa512, 1, [], nil, nil, digest: "md5")
+    }
+  end
+
+  def test_dsig_algorithm_mismatch
+    assert_raise(OpenSSL::X509::CertificateError) do
+      issue_cert(@ca, @rsa2048, 1, [], nil, nil, digest: OpenSSL::Digest::DSS1.new)
+    end if OpenSSL::OPENSSL_VERSION_NUMBER < 0x10001000 # [ruby-core:42949]
+  end
+
+  def test_dsa_with_sha2
+    begin
+      cert = issue_cert(@ca, @dsa256, 1, [], nil, nil, digest: "sha256")
+      assert_equal("dsa_with_SHA256", cert.signature_algorithm)
+    rescue OpenSSL::X509::CertificateError
+      # dsa_with_sha2 not supported. skip following test.
+      return
+    end
+    # TODO: need more tests for dsa + sha2
+
+    # SHA1 is allowed from OpenSSL 1.0.0 (0.9.8 requires DSS1)
+    cert = issue_cert(@ca, @dsa256, 1, [], nil, nil, digest: "sha1")
+    assert_equal("dsaWithSHA1", cert.signature_algorithm)
+  end if defined?(OpenSSL::Digest::SHA256)
+
+  def test_check_private_key
+    cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil)
+    assert_equal(true, cert.check_private_key(@rsa2048))
+  end
+
+  private
+
+  def certificate_error_returns_false
+    yield
+  rescue OpenSSL::X509::CertificateError
+    false
+  end
+end
+
+end
diff --git a/test/test_x509crl.rb b/test/test_x509crl.rb
new file mode 100644
index 0000000..44dfffc
--- /dev/null
+++ b/test/test_x509crl.rb
@@ -0,0 +1,217 @@
+# frozen_string_literal: false
+require_relative "utils"
+
+if defined?(OpenSSL::TestUtils)
+
+class OpenSSL::TestX509CRL < OpenSSL::TestCase
+  def setup
+    super
+    @rsa1024 = OpenSSL::TestUtils::TEST_KEY_RSA1024
+    @rsa2048 = OpenSSL::TestUtils::TEST_KEY_RSA2048
+    @dsa256  = OpenSSL::TestUtils::TEST_KEY_DSA256
+    @dsa512  = OpenSSL::TestUtils::TEST_KEY_DSA512
+    @ca = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=CA")
+    @ee1 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=EE1")
+    @ee2 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=EE2")
+  end
+
+  def issue_crl(*args)
+    OpenSSL::TestUtils.issue_crl(*args)
+  end
+
+  def issue_cert(*args)
+    OpenSSL::TestUtils.issue_cert(*args)
+  end
+
+  def test_basic
+    now = Time.at(Time.now.to_i)
+
+    cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil)
+    crl = issue_crl([], 1, now, now+1600, [],
+                    cert, @rsa2048, OpenSSL::Digest::SHA1.new)
+    assert_equal(1, crl.version)
+    assert_equal(cert.issuer.to_der, crl.issuer.to_der)
+    assert_equal(now, crl.last_update)
+    assert_equal(now+1600, crl.next_update)
+
+    crl = OpenSSL::X509::CRL.new(crl.to_der)
+    assert_equal(1, crl.version)
+    assert_equal(cert.issuer.to_der, crl.issuer.to_der)
+    assert_equal(now, crl.last_update)
+    assert_equal(now+1600, crl.next_update)
+  end
+
+  def test_revoked
+
+    # CRLReason ::= ENUMERATED {
+    #      unspecified             (0),
+    #      keyCompromise           (1),
+    #      cACompromise            (2),
+    #      affiliationChanged      (3),
+    #      superseded              (4),
+    #      cessationOfOperation    (5),
+    #      certificateHold         (6),
+    #      removeFromCRL           (8),
+    #      privilegeWithdrawn      (9),
+    #      aACompromise           (10) }
+
+    now = Time.at(Time.now.to_i)
+    revoke_info = [
+      [1, Time.at(0),          1],
+      [2, Time.at(0x7fffffff), 2],
+      [3, now,                 3],
+      [4, now,                 4],
+      [5, now,                 5],
+    ]
+    cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil)
+    crl = issue_crl(revoke_info, 1, Time.now, Time.now+1600, [],
+                    cert, @rsa2048, OpenSSL::Digest::SHA1.new)
+    revoked = crl.revoked
+    assert_equal(5, revoked.size)
+    assert_equal(1, revoked[0].serial)
+    assert_equal(2, revoked[1].serial)
+    assert_equal(3, revoked[2].serial)
+    assert_equal(4, revoked[3].serial)
+    assert_equal(5, revoked[4].serial)
+
+    assert_equal(Time.at(0), revoked[0].time)
+    assert_equal(Time.at(0x7fffffff), revoked[1].time)
+    assert_equal(now, revoked[2].time)
+    assert_equal(now, revoked[3].time)
+    assert_equal(now, revoked[4].time)
+
+    assert_equal("CRLReason", revoked[0].extensions[0].oid)
+    assert_equal("CRLReason", revoked[1].extensions[0].oid)
+    assert_equal("CRLReason", revoked[2].extensions[0].oid)
+    assert_equal("CRLReason", revoked[3].extensions[0].oid)
+    assert_equal("CRLReason", revoked[4].extensions[0].oid)
+
+    assert_equal("Key Compromise", revoked[0].extensions[0].value)
+    assert_equal("CA Compromise", revoked[1].extensions[0].value)
+    assert_equal("Affiliation Changed", revoked[2].extensions[0].value)
+    assert_equal("Superseded", revoked[3].extensions[0].value)
+    assert_equal("Cessation Of Operation", revoked[4].extensions[0].value)
+
+    assert_equal(false, revoked[0].extensions[0].critical?)
+    assert_equal(false, revoked[1].extensions[0].critical?)
+    assert_equal(false, revoked[2].extensions[0].critical?)
+    assert_equal(false, revoked[3].extensions[0].critical?)
+    assert_equal(false, revoked[4].extensions[0].critical?)
+
+    assert_equal("Key Compromise", revoked[0].extensions[0].value)
+    assert_equal("CA Compromise", revoked[1].extensions[0].value)
+    assert_equal("Affiliation Changed", revoked[2].extensions[0].value)
+    assert_equal("Superseded", revoked[3].extensions[0].value)
+    assert_equal("Cessation Of Operation", revoked[4].extensions[0].value)
+
+    revoke_info = (1..1000).collect{|i| [i, now, 0] }
+    crl = issue_crl(revoke_info, 1, Time.now, Time.now+1600, [],
+                    cert, @rsa2048, OpenSSL::Digest::SHA1.new)
+    revoked = crl.revoked
+    assert_equal(1000, revoked.size)
+    assert_equal(1, revoked[0].serial)
+    assert_equal(1000, revoked[999].serial)
+
+    crl.revoked = revoked
+    revoked2 = crl.revoked
+    assert_equal(revoked.map(&:serial), revoked2.map(&:serial))
+  end
+
+  def test_extension
+    cert_exts = [
+      ["basicConstraints", "CA:TRUE", true],
+      ["subjectKeyIdentifier", "hash", false],
+      ["authorityKeyIdentifier", "keyid:always", false],
+      ["subjectAltName", "email:xyzzy at ruby-lang.org", false],
+      ["keyUsage", "cRLSign, keyCertSign", true],
+    ]
+    crl_exts = [
+      ["authorityKeyIdentifier", "keyid:always", false],
+      ["issuerAltName", "issuer:copy", false],
+    ]
+
+    cert = issue_cert(@ca, @rsa2048, 1, cert_exts, nil, nil)
+    crl = issue_crl([], 1, Time.now, Time.now+1600, crl_exts,
+                    cert, @rsa2048, OpenSSL::Digest::SHA1.new)
+    exts = crl.extensions
+    assert_equal(3, exts.size)
+    assert_equal("1", exts[0].value)
+    assert_equal("crlNumber", exts[0].oid)
+    assert_equal(false, exts[0].critical?)
+
+    assert_equal("authorityKeyIdentifier", exts[1].oid)
+    keyid = OpenSSL::TestUtils.get_subject_key_id(cert)
+    assert_match(/^keyid:#{keyid}/, exts[1].value)
+    assert_equal(false, exts[1].critical?)
+
+    assert_equal("issuerAltName", exts[2].oid)
+    assert_equal("email:xyzzy at ruby-lang.org", exts[2].value)
+    assert_equal(false, exts[2].critical?)
+
+    crl = OpenSSL::X509::CRL.new(crl.to_der)
+    exts = crl.extensions
+    assert_equal(3, exts.size)
+    assert_equal("1", exts[0].value)
+    assert_equal("crlNumber", exts[0].oid)
+    assert_equal(false, exts[0].critical?)
+
+    assert_equal("authorityKeyIdentifier", exts[1].oid)
+    keyid = OpenSSL::TestUtils.get_subject_key_id(cert)
+    assert_match(/^keyid:#{keyid}/, exts[1].value)
+    assert_equal(false, exts[1].critical?)
+
+    assert_equal("issuerAltName", exts[2].oid)
+    assert_equal("email:xyzzy at ruby-lang.org", exts[2].value)
+    assert_equal(false, exts[2].critical?)
+  end
+
+  def test_crlnumber
+    cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil)
+    crl = issue_crl([], 1, Time.now, Time.now+1600, [],
+                    cert, @rsa2048, OpenSSL::Digest::SHA1.new)
+    assert_match(1.to_s, crl.extensions[0].value)
+    assert_match(/X509v3 CRL Number:\s+#{1}/m, crl.to_text)
+
+    crl = issue_crl([], 2**32, Time.now, Time.now+1600, [],
+                    cert, @rsa2048, OpenSSL::Digest::SHA1.new)
+    assert_match((2**32).to_s, crl.extensions[0].value)
+    assert_match(/X509v3 CRL Number:\s+#{2**32}/m, crl.to_text)
+
+    crl = issue_crl([], 2**100, Time.now, Time.now+1600, [],
+                    cert, @rsa2048, OpenSSL::Digest::SHA1.new)
+    assert_match(/X509v3 CRL Number:\s+#{2**100}/m, crl.to_text)
+    assert_match((2**100).to_s, crl.extensions[0].value)
+  end
+
+  def test_sign_and_verify
+    cert = issue_cert(@ca, @rsa2048, 1, [], nil, nil)
+    crl = issue_crl([], 1, Time.now, Time.now+1600, [],
+                    cert, @rsa2048, OpenSSL::Digest::SHA1.new)
+    assert_equal(false, crl.verify(@rsa1024))
+    assert_equal(true,  crl.verify(@rsa2048))
+    assert_equal(false, crl_error_returns_false { crl.verify(@dsa256) })
+    assert_equal(false, crl_error_returns_false { crl.verify(@dsa512) })
+    crl.version = 0
+    assert_equal(false, crl.verify(@rsa2048))
+
+    cert = issue_cert(@ca, @dsa512, 1, [], nil, nil)
+    crl = issue_crl([], 1, Time.now, Time.now+1600, [],
+                    cert, @dsa512, OpenSSL::TestUtils::DSA_SIGNATURE_DIGEST.new)
+    assert_equal(false, crl_error_returns_false { crl.verify(@rsa1024) })
+    assert_equal(false, crl_error_returns_false { crl.verify(@rsa2048) })
+    assert_equal(false, crl.verify(@dsa256))
+    assert_equal(true,  crl.verify(@dsa512))
+    crl.version = 0
+    assert_equal(false, crl.verify(@dsa512))
+  end
+
+  private
+
+  def crl_error_returns_false
+    yield
+  rescue OpenSSL::X509::CRLError
+    false
+  end
+end
+
+end
diff --git a/test/test_x509ext.rb b/test/test_x509ext.rb
new file mode 100644
index 0000000..58f0316
--- /dev/null
+++ b/test/test_x509ext.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: false
+require_relative 'utils'
+
+if defined?(OpenSSL::TestUtils)
+
+class OpenSSL::TestX509Extension < OpenSSL::TestCase
+  def setup
+    super
+    @basic_constraints_value = OpenSSL::ASN1::Sequence([
+      OpenSSL::ASN1::Boolean(true),   # CA
+      OpenSSL::ASN1::Integer(2)       # pathlen
+    ])
+    @basic_constraints = OpenSSL::ASN1::Sequence([
+      OpenSSL::ASN1::ObjectId("basicConstraints"),
+      OpenSSL::ASN1::Boolean(true),
+      OpenSSL::ASN1::OctetString(@basic_constraints_value.to_der),
+    ])
+  end
+
+  def test_new
+    ext = OpenSSL::X509::Extension.new(@basic_constraints.to_der)
+    assert_equal("basicConstraints", ext.oid)
+    assert_equal(true, ext.critical?)
+    assert_equal("CA:TRUE, pathlen:2", ext.value)
+
+    ext = OpenSSL::X509::Extension.new("2.5.29.19",
+                                       @basic_constraints_value.to_der, true)
+    assert_equal(@basic_constraints.to_der, ext.to_der)
+  end
+
+  def test_create_by_factory
+    ef = OpenSSL::X509::ExtensionFactory.new
+
+    bc = ef.create_extension("basicConstraints", "critical, CA:TRUE, pathlen:2")
+    assert_equal(@basic_constraints.to_der, bc.to_der)
+
+    bc = ef.create_extension("basicConstraints", "CA:TRUE, pathlen:2", true)
+    assert_equal(@basic_constraints.to_der, bc.to_der)
+
+    ef.config = OpenSSL::Config.parse(<<-_end_of_cnf_)
+    [crlDistPts]
+    URI.1 = http://www.example.com/crl
+    URI.2 = ldap://ldap.example.com/cn=ca?certificateRevocationList;binary
+
+    [certPolicies]
+    policyIdentifier = 2.23.140.1.2.1
+    CPS.1 = http://cps.example.com
+    _end_of_cnf_
+
+    cdp = ef.create_extension("crlDistributionPoints", "@crlDistPts")
+    assert_equal(false, cdp.critical?)
+    assert_equal("crlDistributionPoints", cdp.oid)
+    assert_match(%{URI:http://www\.example\.com/crl}, cdp.value)
+    assert_match(
+      %r{URI:ldap://ldap\.example\.com/cn=ca\?certificateRevocationList;binary},
+      cdp.value)
+
+    cdp = ef.create_extension("crlDistributionPoints", "critical, @crlDistPts")
+    assert_equal(true, cdp.critical?)
+    assert_equal("crlDistributionPoints", cdp.oid)
+    assert_match(%{URI:http://www.example.com/crl}, cdp.value)
+    assert_match(
+      %r{URI:ldap://ldap.example.com/cn=ca\?certificateRevocationList;binary},
+      cdp.value)
+
+    cp = ef.create_extension("certificatePolicies", "@certPolicies")
+    assert_equal(false, cp.critical?)
+    assert_equal("certificatePolicies", cp.oid)
+    assert_match(%r{2.23.140.1.2.1}, cp.value)
+    assert_match(%r{http://cps.example.com}, cp.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/test_x509name.rb b/test/test_x509name.rb
new file mode 100644
index 0000000..60e8ddb
--- /dev/null
+++ b/test/test_x509name.rb
@@ -0,0 +1,376 @@
+# coding: US-ASCII
+# frozen_string_literal: false
+require_relative 'utils'
+
+if defined?(OpenSSL::TestUtils)
+
+class OpenSSL::TestX509Name < OpenSSL::TestCase
+  def setup
+    super
+    @obj_type_tmpl = Hash.new(OpenSSL::ASN1::PRINTABLESTRING)
+    @obj_type_tmpl.update(OpenSSL::X509::Name::OBJECT_TYPE_TEMPLATE)
+  end
+
+  def test_s_new
+    dn = [ ["C", "JP"], ["O", "example"], ["CN", "www.example.jp"] ]
+    name = OpenSSL::X509::Name.new(dn)
+    ary = name.to_a
+    assert_equal("/C=JP/O=example/CN=www.example.jp", name.to_s)
+    assert_equal("C", ary[0][0])
+    assert_equal("O", ary[1][0])
+    assert_equal("CN", ary[2][0])
+    assert_equal("JP", ary[0][1])
+    assert_equal("example", ary[1][1])
+    assert_equal("www.example.jp", ary[2][1])
+    assert_equal(OpenSSL::ASN1::PRINTABLESTRING, ary[0][2])
+    assert_equal(OpenSSL::ASN1::UTF8STRING, ary[1][2])
+    assert_equal(OpenSSL::ASN1::UTF8STRING, ary[2][2])
+
+    dn = [
+      ["countryName", "JP"],
+      ["organizationName", "example"],
+      ["commonName", "www.example.jp"]
+    ]
+    name = OpenSSL::X509::Name.new(dn)
+    ary = name.to_a
+    assert_equal("/C=JP/O=example/CN=www.example.jp", name.to_s)
+    assert_equal("C", ary[0][0])
+    assert_equal("O", ary[1][0])
+    assert_equal("CN", ary[2][0])
+    assert_equal("JP", ary[0][1])
+    assert_equal("example", ary[1][1])
+    assert_equal("www.example.jp", ary[2][1])
+    assert_equal(OpenSSL::ASN1::PRINTABLESTRING, ary[0][2])
+    assert_equal(OpenSSL::ASN1::UTF8STRING, ary[1][2])
+    assert_equal(OpenSSL::ASN1::UTF8STRING, ary[2][2])
+
+    name = OpenSSL::X509::Name.new(dn, @obj_type_tmpl)
+    ary = name.to_a
+    assert_equal("/C=JP/O=example/CN=www.example.jp", name.to_s)
+    assert_equal(OpenSSL::ASN1::PRINTABLESTRING, ary[0][2])
+    assert_equal(OpenSSL::ASN1::PRINTABLESTRING, ary[1][2])
+    assert_equal(OpenSSL::ASN1::PRINTABLESTRING, ary[2][2])
+
+    dn = [
+      ["countryName", "JP", OpenSSL::ASN1::PRINTABLESTRING],
+      ["organizationName", "example", OpenSSL::ASN1::PRINTABLESTRING],
+      ["commonName", "www.example.jp", OpenSSL::ASN1::PRINTABLESTRING]
+    ]
+    name = OpenSSL::X509::Name.new(dn)
+    ary = name.to_a
+    assert_equal("/C=JP/O=example/CN=www.example.jp", name.to_s)
+    assert_equal(OpenSSL::ASN1::PRINTABLESTRING, ary[0][2])
+    assert_equal(OpenSSL::ASN1::PRINTABLESTRING, ary[1][2])
+    assert_equal(OpenSSL::ASN1::PRINTABLESTRING, ary[2][2])
+
+    dn = [
+      ["DC", "org"],
+      ["DC", "ruby-lang"],
+      ["CN", "GOTOU Yuuzou"],
+      ["emailAddress", "gotoyuzo at ruby-lang.org"],
+      ["serialNumber", "123"],
+    ]
+    name = OpenSSL::X509::Name.new(dn)
+    ary = name.to_a
+    assert_equal("/DC=org/DC=ruby-lang/CN=GOTOU Yuuzou/emailAddress=gotoyuzo at ruby-lang.org/serialNumber=123", name.to_s)
+    assert_equal("DC", ary[0][0])
+    assert_equal("DC", ary[1][0])
+    assert_equal("CN", ary[2][0])
+    assert_equal("emailAddress", ary[3][0])
+    assert_equal("serialNumber", ary[4][0])
+    assert_equal("org", ary[0][1])
+    assert_equal("ruby-lang", ary[1][1])
+    assert_equal("GOTOU Yuuzou", ary[2][1])
+    assert_equal("gotoyuzo at ruby-lang.org", ary[3][1])
+    assert_equal("123", ary[4][1])
+    assert_equal(OpenSSL::ASN1::IA5STRING, ary[0][2])
+    assert_equal(OpenSSL::ASN1::IA5STRING, ary[1][2])
+    assert_equal(OpenSSL::ASN1::UTF8STRING, ary[2][2])
+    assert_equal(OpenSSL::ASN1::IA5STRING, ary[3][2])
+    assert_equal(OpenSSL::ASN1::PRINTABLESTRING, ary[4][2])
+
+    name_from_der = OpenSSL::X509::Name.new(name.to_der)
+    assert_equal(name_from_der.to_s, name.to_s)
+    assert_equal(name_from_der.to_a, name.to_a)
+    assert_equal(name_from_der.to_der, name.to_der)
+  end
+
+  def test_unrecognized_oid
+    dn = [ ["1.2.3.4.5.6.7.8.9.7.5.3.1", "Unknown OID 1"],
+           ["1.1.2.3.5.8.13.21.34", "Unknown OID 2"],
+           ["C", "US"],
+           ["postalCode", "60602"],
+           ["ST", "Illinois"],
+           ["L", "Chicago"],
+           #["street", "123 Fake St"],
+           ["O", "Some Company LLC"],
+           ["CN", "mydomain.com"] ]
+
+    name = OpenSSL::X509::Name.new(dn)
+    ary = name.to_a
+    #assert_equal("/1.2.3.4.5.6.7.8.9.7.5.3.1=Unknown OID 1/1.1.2.3.5.8.13.21.34=Unknown OID 2/C=US/postalCode=60602/ST=Illinois/L=Chicago/street=123 Fake St/O=Some Company LLC/CN=mydomain.com", name.to_s)
+    assert_equal("/1.2.3.4.5.6.7.8.9.7.5.3.1=Unknown OID 1/1.1.2.3.5.8.13.21.34=Unknown OID 2/C=US/postalCode=60602/ST=Illinois/L=Chicago/O=Some Company LLC/CN=mydomain.com", name.to_s)
+    assert_equal("1.2.3.4.5.6.7.8.9.7.5.3.1", ary[0][0])
+    assert_equal("1.1.2.3.5.8.13.21.34", ary[1][0])
+    assert_equal("C", ary[2][0])
+    assert_equal("postalCode", ary[3][0])
+    assert_equal("ST", ary[4][0])
+    assert_equal("L", ary[5][0])
+    #assert_equal("street", ary[6][0])
+    assert_equal("O", ary[6][0])
+    assert_equal("CN", ary[7][0])
+    assert_equal("Unknown OID 1", ary[0][1])
+    assert_equal("Unknown OID 2", ary[1][1])
+    assert_equal("US", ary[2][1])
+    assert_equal("60602", ary[3][1])
+    assert_equal("Illinois", ary[4][1])
+    assert_equal("Chicago", ary[5][1])
+    #assert_equal("123 Fake St", ary[6][1])
+    assert_equal("Some Company LLC", ary[6][1])
+    assert_equal("mydomain.com", ary[7][1])
+  end
+
+  def test_unrecognized_oid_parse_encode_equality
+    dn = [ ["1.2.3.4.5.6.7.8.9.7.5.3.2", "Unknown OID1"],
+           ["1.1.2.3.5.8.13.21.35", "Unknown OID2"],
+           ["C", "US"],
+           ["postalCode", "60602"],
+           ["ST", "Illinois"],
+           ["L", "Chicago"],
+           #["street", "123 Fake St"],
+           ["O", "Some Company LLC"],
+           ["CN", "mydomain.com"] ]
+
+    name1 = OpenSSL::X509::Name.new(dn)
+    name2 = OpenSSL::X509::Name.parse(name1.to_s)
+    assert_equal(name1.to_s, name2.to_s)
+    assert_equal(name1.to_a, name2.to_a)
+  end
+
+  def test_s_parse
+    dn = "/DC=org/DC=ruby-lang/CN=www.ruby-lang.org"
+    name = OpenSSL::X509::Name.parse(dn)
+    assert_equal(dn, name.to_s)
+    ary = name.to_a
+    assert_equal("DC", ary[0][0])
+    assert_equal("DC", ary[1][0])
+    assert_equal("CN", ary[2][0])
+    assert_equal("org", ary[0][1])
+    assert_equal("ruby-lang", ary[1][1])
+    assert_equal("www.ruby-lang.org", ary[2][1])
+    assert_equal(OpenSSL::ASN1::IA5STRING, ary[0][2])
+    assert_equal(OpenSSL::ASN1::IA5STRING, ary[1][2])
+    assert_equal(OpenSSL::ASN1::UTF8STRING, ary[2][2])
+
+    dn2 = "DC=org, DC=ruby-lang, CN=www.ruby-lang.org"
+    name = OpenSSL::X509::Name.parse(dn2)
+    ary = name.to_a
+    assert_equal(dn, name.to_s)
+    assert_equal("org", ary[0][1])
+    assert_equal("ruby-lang", ary[1][1])
+    assert_equal("www.ruby-lang.org", ary[2][1])
+
+    name = OpenSSL::X509::Name.parse(dn2, @obj_type_tmpl)
+    ary = name.to_a
+    assert_equal(OpenSSL::ASN1::IA5STRING, ary[0][2])
+    assert_equal(OpenSSL::ASN1::IA5STRING, ary[1][2])
+    assert_equal(OpenSSL::ASN1::PRINTABLESTRING, ary[2][2])
+  end
+
+  def test_s_parse_rfc2253
+    scanner = OpenSSL::X509::Name::RFC2253DN.method(:scan)
+
+    assert_equal([["C", "JP"]], scanner.call("C=JP"))
+    assert_equal([
+        ["DC", "org"],
+        ["DC", "ruby-lang"],
+        ["CN", "GOTOU Yuuzou"],
+        ["emailAddress", "gotoyuzo at ruby-lang.org"],
+      ],
+      scanner.call(
+        "emailAddress=gotoyuzo at ruby-lang.org,CN=GOTOU Yuuzou,"+
+        "DC=ruby-lang,DC=org")
+    )
+
+    u8 = OpenSSL::ASN1::UTF8STRING
+    assert_equal([
+        ["DC", "org"],
+        ["DC", "ruby-lang"],
+        ["O", ",=+<>#;"],
+        ["O", ",=+<>#;"],
+        ["OU", ""],
+        ["OU", ""],
+        ["L", "aaa=\"bbb, ccc\""],
+        ["L", "aaa=\"bbb, ccc\""],
+        ["CN", "\345\276\214\350\227\244\350\243\225\350\224\265"],
+        ["CN", "\345\276\214\350\227\244\350\243\225\350\224\265"],
+        ["CN", "\345\276\214\350\227\244\350\243\225\350\224\265"],
+        ["CN", "\345\276\214\350\227\244\350\243\225\350\224\265", u8],
+        ["2.5.4.3", "GOTOU, Yuuzou"],
+        ["2.5.4.3", "GOTOU, Yuuzou"],
+        ["2.5.4.3", "GOTOU, Yuuzou"],
+        ["2.5.4.3", "GOTOU, Yuuzou"],
+        ["CN", "GOTOU \"gotoyuzo\" Yuuzou"],
+        ["CN", "GOTOU \"gotoyuzo\" Yuuzou"],
+        ["1.2.840.113549.1.9.1", "gotoyuzo at ruby-lang.org"],
+        ["emailAddress", "gotoyuzo at ruby-lang.org"],
+      ],
+      scanner.call(
+        "emailAddress=gotoyuzo at ruby-lang.org," +
+        "1.2.840.113549.1.9.1=gotoyuzo at ruby-lang.org," +
+        'CN=GOTOU \"gotoyuzo\" Yuuzou,' +
+        'CN="GOTOU \"gotoyuzo\" Yuuzou",' +
+        '2.5.4.3=GOTOU\,\20Yuuzou,' +
+        '2.5.4.3=GOTOU\, Yuuzou,' +
+        '2.5.4.3="GOTOU, Yuuzou",' +
+        '2.5.4.3="GOTOU\, Yuuzou",' +
+        "CN=#0C0CE5BE8CE897A4E8A395E894B5," +
+        'CN=\E5\BE\8C\E8\97\A4\E8\A3\95\E8\94\B5,' +
+        "CN=\"\xE5\xBE\x8C\xE8\x97\xA4\xE8\xA3\x95\xE8\x94\xB5\"," +
+        "CN=\xE5\xBE\x8C\xE8\x97\xA4\xE8\xA3\x95\xE8\x94\xB5," +
+        'L=aaa\=\"bbb\, ccc\",' +
+        'L="aaa=\"bbb, ccc\"",' +
+        'OU=,' +
+        'OU="",' +
+        'O=\,\=\+\<\>\#\;,' +
+        'O=",=+<>#;",' +
+        "DC=ruby-lang," +
+        "DC=org")
+    )
+
+    [
+      "DC=org+DC=jp",
+      "DC=org,DC=ruby-lang+DC=rubyist,DC=www"
+    ].each{|dn|
+      ex = scanner.call(dn) rescue $!
+      dn_r = Regexp.escape(dn)
+      assert_match(/^multi-valued RDN is not supported: #{dn_r}/, ex.message)
+    }
+
+    [
+      ["DC=org,DC=exapmle,CN", "CN"],
+      ["DC=org,DC=example,", ""],
+      ["DC=org,DC=exapmle,CN=www.example.org;", "CN=www.example.org;"],
+      ["DC=org,DC=exapmle,CN=#www.example.org", "CN=#www.example.org"],
+      ["DC=org,DC=exapmle,CN=#777777.example.org", "CN=#777777.example.org"],
+      ["DC=org,DC=exapmle,CN=\"www.example\".org", "CN=\"www.example\".org"],
+      ["DC=org,DC=exapmle,CN=www.\"example.org\"", "CN=www.\"example.org\""],
+      ["DC=org,DC=exapmle,CN=www.\"example\".org", "CN=www.\"example\".org"],
+    ].each{|dn, msg|
+      ex = scanner.call(dn) rescue $!
+      assert_match(/^malformed RDN: .*=>#{Regexp.escape(msg)}/, ex.message)
+    }
+
+    dn = "CN=www.ruby-lang.org,DC=ruby-lang,DC=org"
+    name = OpenSSL::X509::Name.parse_rfc2253(dn)
+    assert_equal(dn, name.to_s(OpenSSL::X509::Name::RFC2253))
+    ary = name.to_a
+    assert_equal("DC", ary[0][0])
+    assert_equal("DC", ary[1][0])
+    assert_equal("CN", ary[2][0])
+    assert_equal("org", ary[0][1])
+    assert_equal("ruby-lang", ary[1][1])
+    assert_equal("www.ruby-lang.org", ary[2][1])
+    assert_equal(OpenSSL::ASN1::IA5STRING, ary[0][2])
+    assert_equal(OpenSSL::ASN1::IA5STRING, ary[1][2])
+    assert_equal(OpenSSL::ASN1::UTF8STRING, ary[2][2])
+  end
+
+  def test_add_entry
+    dn = [
+      ["DC", "org"],
+      ["DC", "ruby-lang"],
+      ["CN", "GOTOU Yuuzou"],
+      ["emailAddress", "gotoyuzo at ruby-lang.org"],
+      ["serialNumber", "123"],
+    ]
+    name = OpenSSL::X509::Name.new
+    dn.each{|attr| name.add_entry(*attr) }
+    ary = name.to_a
+    assert_equal("/DC=org/DC=ruby-lang/CN=GOTOU Yuuzou/emailAddress=gotoyuzo at ruby-lang.org/serialNumber=123", name.to_s)
+    assert_equal("DC", ary[0][0])
+    assert_equal("DC", ary[1][0])
+    assert_equal("CN", ary[2][0])
+    assert_equal("emailAddress", ary[3][0])
+    assert_equal("serialNumber", ary[4][0])
+    assert_equal("org", ary[0][1])
+    assert_equal("ruby-lang", ary[1][1])
+    assert_equal("GOTOU Yuuzou", ary[2][1])
+    assert_equal("gotoyuzo at ruby-lang.org", ary[3][1])
+    assert_equal("123", ary[4][1])
+    assert_equal(OpenSSL::ASN1::IA5STRING, ary[0][2])
+    assert_equal(OpenSSL::ASN1::IA5STRING, ary[1][2])
+    assert_equal(OpenSSL::ASN1::UTF8STRING, ary[2][2])
+    assert_equal(OpenSSL::ASN1::IA5STRING, ary[3][2])
+    assert_equal(OpenSSL::ASN1::PRINTABLESTRING, ary[4][2])
+  end
+
+  def test_add_entry_street
+    return if OpenSSL::OPENSSL_VERSION_NUMBER < 0x009080df # 0.9.8m
+    # openssl/crypto/objects/obj_mac.h 1.83
+    dn = [
+      ["DC", "org"],
+      ["DC", "ruby-lang"],
+      ["CN", "GOTOU Yuuzou"],
+      ["emailAddress", "gotoyuzo at ruby-lang.org"],
+      ["serialNumber", "123"],
+      ["street", "Namiki"],
+    ]
+    name = OpenSSL::X509::Name.new
+    dn.each{|attr| name.add_entry(*attr) }
+    ary = name.to_a
+    assert_equal("/DC=org/DC=ruby-lang/CN=GOTOU Yuuzou/emailAddress=gotoyuzo at ruby-lang.org/serialNumber=123/street=Namiki", name.to_s)
+    assert_equal("Namiki", ary[5][1])
+  end
+
+  def test_equals2
+    n1 = OpenSSL::X509::Name.parse 'CN=a'
+    n2 = OpenSSL::X509::Name.parse 'CN=a'
+
+    assert_equal n1, n2
+  end
+
+  def test_spaceship
+    n1 = OpenSSL::X509::Name.parse 'CN=a'
+    n2 = OpenSSL::X509::Name.parse 'CN=b'
+
+    assert_equal(-1, n1 <=> n2)
+  end
+
+  def name_hash(name)
+    # OpenSSL 1.0.0 uses SHA1 for canonical encoding (not just a der) of
+    # X509Name for X509_NAME_hash.
+    name.respond_to?(:hash_old) ? name.hash_old : name.hash
+  end
+
+  def test_hash
+    dn = "/DC=org/DC=ruby-lang/CN=www.ruby-lang.org"
+    name = OpenSSL::X509::Name.parse(dn)
+    d = OpenSSL::Digest::MD5.digest(name.to_der)
+    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))
+    #
+    dn = "/DC=org/DC=ruby-lang/CN=baz.ruby-lang.org"
+    name = OpenSSL::X509::Name.parse(dn)
+    d = OpenSSL::Digest::MD5.digest(name.to_der)
+    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_equality
+    name0 = OpenSSL::X509::Name.new([["DC", "org"], ["DC", "ruby-lang"], ["CN", "bar.ruby-lang.org"]])
+    name1 = OpenSSL::X509::Name.new([["DC", "org"], ["DC", "ruby-lang"], ["CN", "bar.ruby-lang.org"]])
+    name2 = OpenSSL::X509::Name.new([["DC", "org"], ["DC", "ruby-lang"], ["CN", "baz.ruby-lang.org"]])
+    assert_equal true, name0 == name1
+    assert_equal true, name0.eql?(name1)
+    assert_equal false, name0 == name2
+    assert_equal false, name0.eql?(name2)
+  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/test_x509req.rb b/test/test_x509req.rb
new file mode 100644
index 0000000..585dda1
--- /dev/null
+++ b/test/test_x509req.rb
@@ -0,0 +1,165 @@
+# frozen_string_literal: false
+require_relative "utils"
+
+if defined?(OpenSSL::TestUtils)
+
+class OpenSSL::TestX509Request < OpenSSL::TestCase
+  def setup
+    super
+    @rsa1024 = OpenSSL::TestUtils::TEST_KEY_RSA1024
+    @rsa2048 = OpenSSL::TestUtils::TEST_KEY_RSA2048
+    @dsa256  = OpenSSL::TestUtils::TEST_KEY_DSA256
+    @dsa512  = OpenSSL::TestUtils::TEST_KEY_DSA512
+    @dn = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=GOTOU Yuuzou")
+  end
+
+  def issue_csr(ver, dn, key, digest)
+    req = OpenSSL::X509::Request.new
+    req.version = ver
+    req.subject = dn
+    req.public_key = key.public_key
+    req.sign(key, digest)
+    req
+  end
+
+  def test_public_key
+    req = issue_csr(0, @dn, @rsa1024, OpenSSL::Digest::SHA1.new)
+    assert_equal(@rsa1024.public_key.to_der, req.public_key.to_der)
+    req = OpenSSL::X509::Request.new(req.to_der)
+    assert_equal(@rsa1024.public_key.to_der, req.public_key.to_der)
+
+    req = issue_csr(0, @dn, @dsa512, OpenSSL::TestUtils::DSA_SIGNATURE_DIGEST.new)
+    assert_equal(@dsa512.public_key.to_der, req.public_key.to_der)
+    req = OpenSSL::X509::Request.new(req.to_der)
+    assert_equal(@dsa512.public_key.to_der, req.public_key.to_der)
+  end
+
+  def test_version
+    req = issue_csr(0, @dn, @rsa1024, OpenSSL::Digest::SHA1.new)
+    assert_equal(0, req.version)
+    req = OpenSSL::X509::Request.new(req.to_der)
+    assert_equal(0, req.version)
+
+    req = issue_csr(1, @dn, @rsa1024, OpenSSL::Digest::SHA1.new)
+    assert_equal(1, req.version)
+    req = OpenSSL::X509::Request.new(req.to_der)
+    assert_equal(1, req.version)
+  end
+
+  def test_subject
+    req = issue_csr(0, @dn, @rsa1024, OpenSSL::Digest::SHA1.new)
+    assert_equal(@dn.to_der, req.subject.to_der)
+    req = OpenSSL::X509::Request.new(req.to_der)
+    assert_equal(@dn.to_der, req.subject.to_der)
+  end
+
+  def create_ext_req(exts)
+    ef = OpenSSL::X509::ExtensionFactory.new
+    exts = exts.collect{|e| ef.create_extension(*e) }
+    return OpenSSL::ASN1::Set([OpenSSL::ASN1::Sequence(exts)])
+  end
+
+  def get_ext_req(ext_req_value)
+    set = OpenSSL::ASN1.decode(ext_req_value)
+    seq = set.value[0]
+    seq.value.collect{|asn1ext|
+      OpenSSL::X509::Extension.new(asn1ext).to_a
+    }
+  end
+
+  def test_attr
+    exts = [
+      ["keyUsage", "Digital Signature, Key Encipherment", true],
+      ["subjectAltName", "email:gotoyuzo at ruby-lang.org", false],
+    ]
+    attrval = create_ext_req(exts)
+    attrs = [
+      OpenSSL::X509::Attribute.new("extReq", attrval),
+      OpenSSL::X509::Attribute.new("msExtReq", attrval),
+    ]
+
+    req0 = issue_csr(0, @dn, @rsa1024, OpenSSL::Digest::SHA1.new)
+    attrs.each{|attr| req0.add_attribute(attr) }
+    req1 = issue_csr(0, @dn, @rsa1024, OpenSSL::Digest::SHA1.new)
+    req1.attributes = attrs
+    assert_equal(req0.to_der, req1.to_der)
+
+    attrs = req0.attributes
+    assert_equal(2, attrs.size)
+    assert_equal("extReq", attrs[0].oid)
+    assert_equal("msExtReq", attrs[1].oid)
+    assert_equal(exts, get_ext_req(attrs[0].value))
+    assert_equal(exts, get_ext_req(attrs[1].value))
+
+    req = OpenSSL::X509::Request.new(req0.to_der)
+    attrs = req.attributes
+    assert_equal(2, attrs.size)
+    assert_equal("extReq", attrs[0].oid)
+    assert_equal("msExtReq", attrs[1].oid)
+    assert_equal(exts, get_ext_req(attrs[0].value))
+    assert_equal(exts, get_ext_req(attrs[1].value))
+  end
+
+  def test_sign_and_verify_rsa_sha1
+    req = issue_csr(0, @dn, @rsa1024, OpenSSL::Digest::SHA1.new)
+    assert_equal(true,  req.verify(@rsa1024))
+    assert_equal(false, req.verify(@rsa2048))
+    assert_equal(false, request_error_returns_false { req.verify(@dsa256) })
+    assert_equal(false, request_error_returns_false { req.verify(@dsa512) })
+    req.version = 1
+    assert_equal(false, req.verify(@rsa1024))
+  end
+
+  def test_sign_and_verify_rsa_md5
+    req = issue_csr(0, @dn, @rsa2048, OpenSSL::Digest::MD5.new)
+    assert_equal(false, req.verify(@rsa1024))
+    assert_equal(true,  req.verify(@rsa2048))
+    assert_equal(false, request_error_returns_false { req.verify(@dsa256) })
+    assert_equal(false, request_error_returns_false { req.verify(@dsa512) })
+    req.subject = OpenSSL::X509::Name.parse("/C=JP/CN=FooBar")
+    assert_equal(false, req.verify(@rsa2048))
+  rescue OpenSSL::X509::RequestError # RHEL7 disables MD5
+  end
+
+  def test_sign_and_verify_dsa
+    req = issue_csr(0, @dn, @dsa512, OpenSSL::TestUtils::DSA_SIGNATURE_DIGEST.new)
+    assert_equal(false, request_error_returns_false { req.verify(@rsa1024) })
+    assert_equal(false, request_error_returns_false { req.verify(@rsa2048) })
+    assert_equal(false, req.verify(@dsa256))
+    assert_equal(true,  req.verify(@dsa512))
+    req.public_key = @rsa1024.public_key
+    assert_equal(false, req.verify(@dsa512))
+  end
+
+  def test_sign_and_verify_rsa_dss1
+    req = issue_csr(0, @dn, @rsa1024, OpenSSL::Digest::DSS1.new)
+    assert_equal(true,  req.verify(@rsa1024))
+    assert_equal(false, req.verify(@rsa2048))
+    assert_equal(false, request_error_returns_false { req.verify(@dsa256) })
+    assert_equal(false, request_error_returns_false { req.verify(@dsa512) })
+    req.version = 1
+    assert_equal(false, req.verify(@rsa1024))
+  rescue OpenSSL::X509::RequestError
+    pend
+  end if defined?(OpenSSL::Digest::DSS1)
+
+  def test_sign_and_verify_dsa_md5
+    assert_raise(OpenSSL::X509::RequestError){
+      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
+    yield
+  rescue OpenSSL::X509::RequestError
+    false
+  end
+end
+
+end
diff --git a/test/test_x509store.rb b/test/test_x509store.rb
new file mode 100644
index 0000000..c45233a
--- /dev/null
+++ b/test/test_x509store.rb
@@ -0,0 +1,257 @@
+# frozen_string_literal: false
+require_relative "utils"
+
+if defined?(OpenSSL::TestUtils)
+
+class OpenSSL::TestX509Store < OpenSSL::TestCase
+  def setup
+    super
+    @rsa1024 = OpenSSL::TestUtils::TEST_KEY_RSA1024
+    @rsa2048 = OpenSSL::TestUtils::TEST_KEY_RSA2048
+    @dsa256  = OpenSSL::TestUtils::TEST_KEY_DSA256
+    @dsa512  = OpenSSL::TestUtils::TEST_KEY_DSA512
+    @ca1 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=CA1")
+    @ca2 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=CA2")
+    @ee1 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=EE1")
+    @ee2 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=EE2")
+  end
+
+  def test_nosegv_on_cleanup
+    cert  = OpenSSL::X509::Certificate.new
+    store = OpenSSL::X509::Store.new
+    ctx   = OpenSSL::X509::StoreContext.new(store, cert, [])
+    EnvUtil.suppress_warning do
+      ctx.cleanup
+    end
+    ctx.verify
+  end
+
+  def issue_cert(*args)
+    OpenSSL::TestUtils.issue_cert(*args)
+  end
+
+  def issue_crl(*args)
+    OpenSSL::TestUtils.issue_crl(*args)
+  end
+
+  def test_add_file
+    ca_exts = [
+      ["basicConstraints", "CA:TRUE", true],
+      ["keyUsage", "cRLSign,keyCertSign", true],
+    ]
+    cert1 = issue_cert(@ca1, @rsa1024, 1, ca_exts, nil, nil)
+    cert2 = issue_cert(@ca2, @rsa2048, 1, ca_exts, nil, nil)
+    tmpfile = Tempfile.open { |f| f << cert1.to_pem << cert2.to_pem; f }
+
+    store = OpenSSL::X509::Store.new
+    assert_equal false, store.verify(cert1)
+    assert_equal false, store.verify(cert2)
+    store.add_file(tmpfile.path)
+    assert_equal true, store.verify(cert1)
+    assert_equal true, store.verify(cert2)
+
+    # OpenSSL < 1.1.1 leaks an error on a duplicate certificate
+    assert_nothing_raised { store.add_file(tmpfile.path) }
+    assert_equal [], OpenSSL.errors
+  ensure
+    tmpfile and tmpfile.close!
+  end
+
+  def test_verify
+    # OpenSSL uses time(2) while Time.now uses clock_gettime(CLOCK_REALTIME),
+    # and there may be difference.
+    now = Time.now - 3
+    ca_exts = [
+      ["basicConstraints","CA:TRUE",true],
+      ["keyUsage","cRLSign,keyCertSign",true],
+    ]
+    ee_exts = [
+      ["keyUsage","keyEncipherment,digitalSignature",true],
+    ]
+    ca1_cert = issue_cert(@ca1, @rsa2048, 1, ca_exts, nil, nil)
+    ca2_cert = issue_cert(@ca2, @rsa1024, 2, ca_exts, ca1_cert, @rsa2048,
+                          not_after: now+1800)
+    ee1_cert = issue_cert(@ee1, @dsa256, 10, ee_exts, ca2_cert, @rsa1024)
+    ee2_cert = issue_cert(@ee2, @dsa512, 20, ee_exts, ca2_cert, @rsa1024)
+    ee3_cert = issue_cert(@ee2, @dsa512, 30,  ee_exts, ca2_cert, @rsa1024,
+                          not_before: now-100, not_after: now-1)
+    ee4_cert = issue_cert(@ee2, @dsa512, 40, ee_exts, ca2_cert, @rsa1024,
+                          not_before: now+1000, not_after: now+2000,)
+
+    revoke_info = []
+    crl1   = issue_crl(revoke_info, 1, now, now+1800, [],
+                       ca1_cert, @rsa2048, OpenSSL::Digest::SHA1.new)
+    revoke_info = [ [2, now, 1], ]
+    crl1_2 = issue_crl(revoke_info, 2, now, now+1800, [],
+                       ca1_cert, @rsa2048, OpenSSL::Digest::SHA1.new)
+    revoke_info = [ [20, now, 1], ]
+    crl2   = issue_crl(revoke_info, 1, now, now+1800, [],
+                       ca2_cert, @rsa1024, OpenSSL::Digest::SHA1.new)
+    revoke_info = []
+    crl2_2 = issue_crl(revoke_info, 2, now-100, now-1, [],
+                       ca2_cert, @rsa1024, OpenSSL::Digest::SHA1.new)
+
+    assert_equal(true, ca1_cert.verify(ca1_cert.public_key))   # self signed
+    assert_equal(true, ca2_cert.verify(ca1_cert.public_key))   # issued by ca1
+    assert_equal(true, ee1_cert.verify(ca2_cert.public_key))   # issued by ca2
+    assert_equal(true, ee2_cert.verify(ca2_cert.public_key))   # issued by ca2
+    assert_equal(true, ee3_cert.verify(ca2_cert.public_key))   # issued by ca2
+    assert_equal(true, crl1.verify(ca1_cert.public_key))       # issued by ca1
+    assert_equal(true, crl1_2.verify(ca1_cert.public_key))     # issued by ca1
+    assert_equal(true, crl2.verify(ca2_cert.public_key))       # issued by ca2
+    assert_equal(true, crl2_2.verify(ca2_cert.public_key))     # issued by ca2
+
+    store = OpenSSL::X509::Store.new
+    assert_equal(false, store.verify(ca1_cert))
+    assert_not_equal(OpenSSL::X509::V_OK, store.error)
+
+    assert_equal(false, store.verify(ca2_cert))
+    assert_not_equal(OpenSSL::X509::V_OK, store.error)
+
+    store.add_cert(ca1_cert)
+    assert_equal(true, store.verify(ca2_cert))
+    assert_equal(OpenSSL::X509::V_OK, store.error)
+    assert_equal("ok", store.error_string)
+    chain = store.chain
+    assert_equal(2, chain.size)
+    assert_equal(@ca2.to_der, chain[0].subject.to_der)
+    assert_equal(@ca1.to_der, chain[1].subject.to_der)
+
+    store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT
+    assert_equal(false, store.verify(ca2_cert))
+    assert_not_equal(OpenSSL::X509::V_OK, store.error)
+
+    store.purpose = OpenSSL::X509::PURPOSE_CRL_SIGN
+    assert_equal(true, store.verify(ca2_cert))
+    assert_equal(OpenSSL::X509::V_OK, store.error)
+
+    store.add_cert(ca2_cert)
+    store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT
+    assert_equal(true, store.verify(ee1_cert))
+    assert_equal(true, store.verify(ee2_cert))
+    assert_equal(OpenSSL::X509::V_OK, store.error)
+    assert_equal("ok", store.error_string)
+    chain = store.chain
+    assert_equal(3, chain.size)
+    assert_equal(@ee2.to_der, chain[0].subject.to_der)
+    assert_equal(@ca2.to_der, chain[1].subject.to_der)
+    assert_equal(@ca1.to_der, chain[2].subject.to_der)
+    assert_equal(false, store.verify(ee3_cert))
+    assert_equal(OpenSSL::X509::V_ERR_CERT_HAS_EXPIRED, store.error)
+    assert_match(/expire/i, store.error_string)
+    assert_equal(false, store.verify(ee4_cert))
+    assert_equal(OpenSSL::X509::V_ERR_CERT_NOT_YET_VALID, store.error)
+    assert_match(/not yet valid/i, store.error_string)
+
+    store = OpenSSL::X509::Store.new
+    store.add_cert(ca1_cert)
+    store.add_cert(ca2_cert)
+    store.time = now + 1500
+    assert_equal(true, store.verify(ca1_cert))
+    assert_equal(true, store.verify(ca2_cert))
+    assert_equal(true, store.verify(ee4_cert))
+    store.time = now + 1900
+    assert_equal(true, store.verify(ca1_cert))
+    assert_equal(false, store.verify(ca2_cert))
+    assert_equal(OpenSSL::X509::V_ERR_CERT_HAS_EXPIRED, store.error)
+    assert_equal(false, store.verify(ee4_cert))
+    assert_equal(OpenSSL::X509::V_ERR_CERT_HAS_EXPIRED, store.error)
+    store.time = now + 4000
+    assert_equal(false, store.verify(ee1_cert))
+    assert_equal(OpenSSL::X509::V_ERR_CERT_HAS_EXPIRED, store.error)
+    assert_equal(false, store.verify(ee4_cert))
+    assert_equal(OpenSSL::X509::V_ERR_CERT_HAS_EXPIRED, store.error)
+
+    # the underlying X509 struct caches the result of the last
+    # verification for signature and not-before. so the following code
+    # rebuilds new objects to avoid site effect.
+    store.time = Time.now - 4000
+    assert_equal(false, store.verify(OpenSSL::X509::Certificate.new(ca2_cert)))
+    assert_equal(OpenSSL::X509::V_ERR_CERT_NOT_YET_VALID, store.error)
+    assert_equal(false, store.verify(OpenSSL::X509::Certificate.new(ee1_cert)))
+    assert_equal(OpenSSL::X509::V_ERR_CERT_NOT_YET_VALID, store.error)
+
+    store = OpenSSL::X509::Store.new
+    store.purpose = OpenSSL::X509::PURPOSE_ANY
+    store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK
+    store.add_cert(ca1_cert)
+    store.add_crl(crl1)   # revoke no cert
+    store.add_crl(crl2)   # revoke ee2_cert
+    assert_equal(true,  store.verify(ca1_cert))
+    assert_equal(true,  store.verify(ca2_cert))
+    assert_equal(true,  store.verify(ee1_cert, [ca2_cert]))
+    assert_equal(false, store.verify(ee2_cert, [ca2_cert]))
+
+    store = OpenSSL::X509::Store.new
+    store.purpose = OpenSSL::X509::PURPOSE_ANY
+    store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK
+    store.add_cert(ca1_cert)
+    store.add_crl(crl1_2) # revoke ca2_cert
+    store.add_crl(crl2)   # revoke ee2_cert
+    assert_equal(true,  store.verify(ca1_cert))
+    assert_equal(false, store.verify(ca2_cert))
+    assert_equal(true,  store.verify(ee1_cert, [ca2_cert]),
+      "This test is expected to be success with OpenSSL 0.9.7c or later.")
+    assert_equal(false, store.verify(ee2_cert, [ca2_cert]))
+
+    store.flags =
+      OpenSSL::X509::V_FLAG_CRL_CHECK|OpenSSL::X509::V_FLAG_CRL_CHECK_ALL
+    assert_equal(true,  store.verify(ca1_cert))
+    assert_equal(false, store.verify(ca2_cert))
+    assert_equal(false, store.verify(ee1_cert, [ca2_cert]))
+    assert_equal(false, store.verify(ee2_cert, [ca2_cert]))
+
+    store = OpenSSL::X509::Store.new
+    store.purpose = OpenSSL::X509::PURPOSE_ANY
+    store.flags =
+      OpenSSL::X509::V_FLAG_CRL_CHECK|OpenSSL::X509::V_FLAG_CRL_CHECK_ALL
+    store.add_cert(ca1_cert)
+    store.add_cert(ca2_cert)
+    store.add_crl(crl1)
+    store.add_crl(crl2_2) # issued by ca2 but expired.
+    assert_equal(true, store.verify(ca1_cert))
+    assert_equal(true, store.verify(ca2_cert))
+    assert_equal(false, store.verify(ee1_cert))
+    assert_equal(OpenSSL::X509::V_ERR_CRL_HAS_EXPIRED, store.error)
+    assert_equal(false, store.verify(ee2_cert))
+  end
+
+  def test_set_errors
+    return if OpenSSL::OPENSSL_VERSION_NUMBER >= 0x10100000
+    now = Time.now
+    ca1_cert = issue_cert(@ca1, @rsa2048, 1, [], nil, nil)
+    store = OpenSSL::X509::Store.new
+    store.add_cert(ca1_cert)
+    assert_raise(OpenSSL::X509::StoreError){
+      store.add_cert(ca1_cert)  # add same certificate twice
+    }
+
+    revoke_info = []
+    crl1 = issue_crl(revoke_info, 1, now, now+1800, [],
+                     ca1_cert, @rsa2048, OpenSSL::Digest::SHA1.new)
+    revoke_info = [ [2, now, 1], ]
+    crl2 = issue_crl(revoke_info, 2, now+1800, now+3600, [],
+                     ca1_cert, @rsa2048, OpenSSL::Digest::SHA1.new)
+    store.add_crl(crl1)
+    if /0\.9\.8.*-rhel/ =~ OpenSSL::OPENSSL_VERSION
+      # RedHat is distributing a patched version of OpenSSL that allows
+      # multiple CRL for a key (multi-crl.patch)
+      assert_nothing_raised do
+        store.add_crl(crl2) # add CRL issued by same CA twice.
+      end
+    else
+      assert_raise(OpenSSL::X509::StoreError){
+        store.add_crl(crl2) # add CRL issued by same CA twice.
+      }
+    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
diff --git a/test/ut_eof.rb b/test/ut_eof.rb
new file mode 100644
index 0000000..6de41c4
--- /dev/null
+++ b/test/ut_eof.rb
@@ -0,0 +1,129 @@
+# frozen_string_literal: false
+require 'test/unit'
+
+module OpenSSL::TestEOF
+  def test_eof_0
+    open_file("") {|f|
+      assert_equal("", f.read(0))
+      assert_equal("", f.read(0))
+      assert_equal("", f.read)
+      assert_equal("", f.read(0))
+      assert_equal("", f.read(0))
+    }
+    open_file("") {|f|
+      assert_nil(f.read(1))
+      assert_equal("", f.read)
+      assert_nil(f.read(1))
+    }
+    open_file("") {|f|
+      s = "x"
+      assert_equal("", f.read(nil, s))
+      assert_equal("", s)
+    }
+    open_file("") {|f|
+      s = "x"
+      assert_nil(f.read(10, s))
+      assert_equal("", s)
+    }
+  end
+
+  def test_eof_0_rw
+    return unless respond_to? :open_file_rw
+    open_file_rw("") {|f|
+      assert_equal("", f.read)
+      assert_equal("", f.read)
+      assert_equal(0, f.syswrite(""))
+      assert_equal("", f.read)
+    }
+  end
+
+  def test_eof_1
+    open_file("a") {|f|
+      assert_equal("", f.read(0))
+      assert_equal("a", f.read(1))
+      assert_equal("" , f.read(0))
+      assert_equal("" , f.read(0))
+      assert_equal("", f.read)
+      assert_equal("", f.read(0))
+      assert_equal("", f.read(0))
+    }
+    open_file("a") {|f|
+      assert_equal("a", f.read(1))
+      assert_nil(f.read(1))
+    }
+    open_file("a") {|f|
+      assert_equal("a", f.read(2))
+      assert_nil(f.read(1))
+      assert_equal("", f.read)
+      assert_nil(f.read(1))
+    }
+    open_file("a") {|f|
+      assert_equal("a", f.read)
+      assert_nil(f.read(1))
+      assert_equal("", f.read)
+      assert_nil(f.read(1))
+    }
+    open_file("a") {|f|
+      assert_equal("a", f.read(2))
+      assert_equal("", f.read)
+      assert_equal("", f.read)
+    }
+    open_file("a") {|f|
+      assert_equal("a", f.read)
+      assert_equal("", f.read(0))
+    }
+    open_file("a") {|f|
+      s = "x"
+      assert_equal("a", f.read(nil, s))
+      assert_equal("a", s)
+    }
+    open_file("a") {|f|
+      s = "x"
+      assert_equal("a", f.read(10, s))
+      assert_equal("a", s)
+    }
+  end
+
+  def test_eof_2
+    open_file("") {|f|
+      assert_equal("", f.read)
+      assert_predicate(f, :eof?)
+    }
+  end
+
+  def test_eof_3
+    open_file("") {|f|
+      assert_predicate(f, :eof?)
+    }
+  end
+
+  module Seek
+    def open_file_seek(content, pos)
+      open_file(content) do |f|
+        f.seek(pos)
+        yield f
+      end
+    end
+
+    def test_eof_0_seek
+      open_file_seek("", 10) {|f|
+        assert_equal(10, f.pos)
+        assert_equal("", f.read(0))
+        assert_equal("", f.read)
+        assert_equal("", f.read(0))
+        assert_equal("", f.read)
+      }
+    end
+
+    def test_eof_1_seek
+      open_file_seek("a", 10) {|f|
+        assert_equal("", f.read)
+        assert_equal("", f.read)
+      }
+      open_file_seek("a", 1) {|f|
+        assert_equal("", f.read)
+        assert_equal("", f.read)
+      }
+    end
+  end
+end
diff --git a/test/utils.rb b/test/utils.rb
new file mode 100644
index 0000000..bbc9c7e
--- /dev/null
+++ b/test/utils.rb
@@ -0,0 +1,380 @@
+# frozen_string_literal: false
+begin
+  require "openssl"
+
+  # Disable FIPS mode for tests for installations
+  # where FIPS mode would be enabled by default.
+  # Has no effect on all other installations.
+  OpenSSL.fips_mode=false
+rescue LoadError
+end
+
+require "test/unit"
+require 'tempfile'
+require "rbconfig"
+require "socket"
+require "envutil"
+
+module OpenSSL::TestUtils
+  TEST_KEY_RSA1024 = OpenSSL::PKey::RSA.new <<-_end_of_pem_
+-----BEGIN RSA PRIVATE KEY-----
+MIICXgIBAAKBgQDLwsSw1ECnPtT+PkOgHhcGA71nwC2/nL85VBGnRqDxOqjVh7Cx
+aKPERYHsk4BPCkE3brtThPWc9kjHEQQ7uf9Y1rbCz0layNqHyywQEVLFmp1cpIt/
+Q3geLv8ZD9pihowKJDyMDiN6ArYUmZczvW4976MU3+l54E6lF/JfFEU5hwIDAQAB
+AoGBAKSl/MQarye1yOysqX6P8fDFQt68VvtXkNmlSiKOGuzyho0M+UVSFcs6k1L0
+maDE25AMZUiGzuWHyaU55d7RXDgeskDMakD1v6ZejYtxJkSXbETOTLDwUWTn618T
+gnb17tU1jktUtU67xK/08i/XodlgnQhs6VoHTuCh3Hu77O6RAkEA7+gxqBuZR572
+74/akiW/SuXm0SXPEviyO1MuSRwtI87B02D0qgV8D1UHRm4AhMnJ8MCs1809kMQE
+JiQUCrp9mQJBANlt2ngBO14us6NnhuAseFDTBzCHXwUUu1YKHpMMmxpnGqaldGgX
+sOZB3lgJsT9VlGf3YGYdkLTNVbogQKlKpB8CQQDiSwkb4vyQfDe8/NpU5Not0fII
+8jsDUCb+opWUTMmfbxWRR3FBNu8wnym/m19N4fFj8LqYzHX4KY0oVPu6qvJxAkEA
+wa5snNekFcqONLIE4G5cosrIrb74sqL8GbGb+KuTAprzj5z1K8Bm0UW9lTjVDjDi
+qRYgZfZSL+x1P/54+xTFSwJAY1FxA/N3QPCXCjPh5YqFxAMQs2VVYTfg+t0MEcJD
+dPMQD5JX6g5HKnHFg2mZtoXQrWmJSn7p8GJK8yNTopEErA==
+-----END RSA PRIVATE KEY-----
+  _end_of_pem_
+
+  TEST_KEY_RSA2048 = OpenSSL::PKey::RSA.new <<-_end_of_pem_
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAuV9ht9J7k4NBs38jOXvvTKY9gW8nLICSno5EETR1cuF7i4pN
+s9I1QJGAFAX0BEO4KbzXmuOvfCpD3CU+Slp1enenfzq/t/e/1IRW0wkJUJUFQign
+4CtrkJL+P07yx18UjyPlBXb81ApEmAB5mrJVSrWmqbjs07JbuS4QQGGXLc+Su96D
+kYKmSNVjBiLxVVSpyZfAY3hD37d60uG+X8xdW5v68JkRFIhdGlb6JL8fllf/A/bl
+NwdJOhVr9mESHhwGjwfSeTDPfd8ZLE027E5lyAVX9KZYcU00mOX+fdxOSnGqS/8J
+DRh0EPHDL15RcJjV2J6vZjPb0rOYGDoMcH+94wIDAQABAoIBAAzsamqfYQAqwXTb
+I0CJtGg6msUgU7HVkOM+9d3hM2L791oGHV6xBAdpXW2H8LgvZHJ8eOeSghR8+dgq
+PIqAffo4x1Oma+FOg3A0fb0evyiACyrOk+EcBdbBeLo/LcvahBtqnDfiUMQTpy6V
+seSoFCwuN91TSCeGIsDpRjbG1vxZgtx+uI+oH5+ytqJOmfCksRDCkMglGkzyfcl0
+Xc5CUhIJ0my53xijEUQl19rtWdMnNnnkdbG8PT3LZlOta5Do86BElzUYka0C6dUc
+VsBDQ0Nup0P6rEQgy7tephHoRlUGTYamsajGJaAo1F3IQVIrRSuagi7+YpSpCqsW
+wORqorkCgYEA7RdX6MDVrbw7LePnhyuaqTiMK+055/R1TqhB1JvvxJ1CXk2rDL6G
+0TLHQ7oGofd5LYiemg4ZVtWdJe43BPZlVgT6lvL/iGo8JnrncB9Da6L7nrq/+Rvj
+XGjf1qODCK+LmreZWEsaLPURIoR/Ewwxb9J2zd0CaMjeTwafJo1CZvcCgYEAyCgb
+aqoWvUecX8VvARfuA593Lsi50t4MEArnOXXcd1RnXoZWhbx5rgO8/ATKfXr0BK/n
+h2GF9PfKzHFm/4V6e82OL7gu/kLy2u9bXN74vOvWFL5NOrOKPM7Kg+9I131kNYOw
+Ivnr/VtHE5s0dY7JChYWE1F3vArrOw3T00a4CXUCgYEA0SqY+dS2LvIzW4cHCe9k
+IQqsT0yYm5TFsUEr4sA3xcPfe4cV8sZb9k/QEGYb1+SWWZ+AHPV3UW5fl8kTbSNb
+v4ng8i8rVVQ0ANbJO9e5CUrepein2MPL0AkOATR8M7t7dGGpvYV0cFk8ZrFx0oId
+U0PgYDotF/iueBWlbsOM430CgYEAqYI95dFyPI5/AiSkY5queeb8+mQH62sdcCCr
+vd/w/CZA/K5sbAo4SoTj8dLk4evU6HtIa0DOP63y071eaxvRpTNqLUOgmLh+D6gS
+Cc7TfLuFrD+WDBatBd5jZ+SoHccVrLR/4L8jeodo5FPW05A+9gnKXEXsTxY4LOUC
+9bS4e1kCgYAqVXZh63JsMwoaxCYmQ66eJojKa47VNrOeIZDZvd2BPVf30glBOT41
+gBoDG3WMPZoQj9pb7uMcrnvs4APj2FIhMU8U15LcPAj59cD6S6rWnAxO8NFK7HQG
+4Jxg3JNNf8ErQoCHb1B3oVdXJkmbJkARoDpBKmTCgKtP8ADYLmVPQw==
+-----END RSA PRIVATE KEY-----
+  _end_of_pem_
+
+  TEST_KEY_DSA256 = OpenSSL::PKey::DSA.new <<-_end_of_pem_
+-----BEGIN DSA PRIVATE KEY-----
+MIH3AgEAAkEAhk2libbY2a8y2Pt21+YPYGZeW6wzaW2yfj5oiClXro9XMR7XWLkE
+9B7XxLNFCS2gmCCdMsMW1HulaHtLFQmB2wIVAM43JZrcgpu6ajZ01VkLc93gu/Ed
+AkAOhujZrrKV5CzBKutKLb0GVyVWmdC7InoNSMZEeGU72rT96IjM59YzoqmD0pGM
+3I1o4cGqg1D1DfM1rQlnN1eSAkBq6xXfEDwJ1mLNxF6q8Zm/ugFYWR5xcX/3wFiT
+b4+EjHP/DbNh9Vm5wcfnDBJ1zKvrMEf2xqngYdrV/3CiGJeKAhRvL57QvJZcQGvn
+ISNX5cMzFHRW3Q==
+-----END DSA PRIVATE KEY-----
+  _end_of_pem_
+
+  TEST_KEY_DSA512 = OpenSSL::PKey::DSA.new <<-_end_of_pem_
+-----BEGIN DSA PRIVATE KEY-----
+MIH4AgEAAkEA5lB4GvEwjrsMlGDqGsxrbqeFRh6o9OWt6FgTYiEEHaOYhkIxv0Ok
+RZPDNwOG997mDjBnvDJ1i56OmS3MbTnovwIVAJgub/aDrSDB4DZGH7UyarcaGy6D
+AkB9HdFw/3td8K4l1FZHv7TCZeJ3ZLb7dF3TWoGUP003RCqoji3/lHdKoVdTQNuR
+S/m6DlCwhjRjiQ/lBRgCLCcaAkEAjN891JBjzpMj4bWgsACmMggFf57DS0Ti+5++
+Q1VB8qkJN7rA7/2HrCR3gTsWNb1YhAsnFsoeRscC+LxXoXi9OAIUBG98h4tilg6S
+55jreJD3Se3slps=
+-----END DSA PRIVATE KEY-----
+  _end_of_pem_
+
+  TEST_KEY_DSA1024 = OpenSSL::PKey::DSA.new <<-_end_of_pem_
+-----BEGIN DSA PRIVATE KEY-----
+MIIBugIBAAKBgQCH9aAoXvWWThIjkA6D+nI1F9ksF9iDq594rkiGNOT9sPDOdB+n
+D+qeeeeloRlj19ymCSADPI0ZLRgkchkAEnY2RnqnhHOjVf/roGgRbW+iQDMbQ9wa
+/pvc6/fAbsu1goE1hBYjm98/sZEeXavj8tR56IXnjF1b6Nx0+sgeUKFKEQIVAMiz
+4BJUFeTtddyM4uadBM7HKLPRAoGAZdLBSYNGiij7vAjesF5mGUKTIgPd+JKuBEDx
+OaBclsgfdoyoF/TMOkIty+PVlYD+//Vl2xnoUEIRaMXHwHfm0r2xUX++oeRaSScg
+YizJdUxe5jvBuBszGPRc/mGpb9YvP0sB+FL1KmuxYmdODfCe51zl8uM/CVhouJ3w
+DjmRGscCgYAuFlfC7p+e8huCKydfcv/beftqjewiOPpQ3u5uI6KPCtCJPpDhs3+4
+IihH2cPsAlqwGF4tlibW1+/z/OZ1AZinPK3y7b2jSJASEaPeEltVzB92hcd1khk2
+jTYcmSsV4VddplOPK9czytR/GbbibxsrhhgZUbd8LPbvIgaiadJ1PgIUBnJ/5vN2
+CVArsEzlPUCbohPvZnE=
+-----END DSA PRIVATE KEY-----
+  _end_of_pem_
+
+if defined?(OpenSSL::PKey::EC)
+
+  TEST_KEY_EC_P256V1 = OpenSSL::PKey::EC.new <<-_end_of_pem_
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIID49FDqcf1O1eO8saTgG70UbXQw9Fqwseliit2aWhH1oAoGCCqGSM49
+AwEHoUQDQgAEFglk2c+oVUIKQ64eZG9bhLNPWB7lSZ/ArK41eGy5wAzU/0G51Xtt
+CeBUl+MahZtn9fO1JKdF4qJmS39dXnpENg==
+-----END EC PRIVATE KEY-----
+  _end_of_pem_
+
+end
+
+  TEST_KEY_DH1024 = OpenSSL::PKey::DH.new <<-_end_of_pem_
+-----BEGIN DH PARAMETERS-----
+MIGHAoGBAKnKQ8MNK6nYZzLrrcuTsLxuiJGXoOO5gT+tljOTbHBuiktdMTITzIY0
+pFxIvjG05D7HoBZQfrR0c92NGWPkAiCkhQKB8JCbPVzwNLDy6DZ0pmofDKrEsYHG
+AQjjxMXhwULlmuR/K+WwlaZPiLIBYalLAZQ7ZbOPeVkJ8ePao0eLAgEC
+-----END DH PARAMETERS-----
+  _end_of_pem_
+
+  TEST_KEY_DH1024.set_key(OpenSSL::BN.new("556AF1598AE69899867CEBA9F29CE4862B884C2B43C9019EA0231908F6EFA785E3C462A6ECB16DF676866E997FFB72B487DC7967C58C3CA38CE974473BF19B2AA5DCBF102735572EBA6F353F6F0BBE7FF1DE1B07FE1381A355C275C33405004317F9491B5955F191F6615A63B30E55A027FB88A1A4B25608E09EEE68A7DF32D", 16),
+                          OpenSSL::BN.new("48561834C67E65FFD2A9B47F41E5E78FDC95C387428FDB1E4B0188B64D1643C3A8D3455B945B7E8C4D166010C7C2CE23BFB9BEF43D0348FE7FA5284B0225E7FE1537546D114E3D8A4411B9B9351AB451E1A358F50ED61B1F00DA29336EEBBD649980AC86D76AF8BBB065298C2052672EEF3EF13AB47A15275FC2836F3AC74CEA", 16))
+
+  DSA_SIGNATURE_DIGEST = OpenSSL::OPENSSL_VERSION_NUMBER > 0x10000000 ?
+                         OpenSSL::Digest::SHA1 :
+                         OpenSSL::Digest::DSS1
+
+  module_function
+
+  def issue_cert(dn, key, serial, extensions, issuer, issuer_key,
+                 not_before: nil, not_after: nil, digest: nil)
+    cert = OpenSSL::X509::Certificate.new
+    issuer = cert unless issuer
+    issuer_key = key unless issuer_key
+    cert.version = 2
+    cert.serial = serial
+    cert.subject = dn
+    cert.issuer = issuer.subject
+    cert.public_key = key.public_key
+    now = Time.now
+    cert.not_before = not_before || now - 3600
+    cert.not_after = not_after || now + 3600
+    ef = OpenSSL::X509::ExtensionFactory.new
+    ef.subject_certificate = cert
+    ef.issuer_certificate = issuer
+    extensions.each{|oid, value, critical|
+      cert.add_extension(ef.create_extension(oid, value, critical))
+    }
+    digest ||= OpenSSL::PKey::DSA === issuer_key ? DSA_SIGNATURE_DIGEST.new : "sha256"
+    cert.sign(issuer_key, digest)
+    cert
+  end
+
+  def issue_crl(revoke_info, serial, lastup, nextup, extensions,
+                issuer, issuer_key, digest)
+    crl = OpenSSL::X509::CRL.new
+    crl.issuer = issuer.subject
+    crl.version = 1
+    crl.last_update = lastup
+    crl.next_update = nextup
+    revoke_info.each{|rserial, time, reason_code|
+      revoked = OpenSSL::X509::Revoked.new
+      revoked.serial = rserial
+      revoked.time = time
+      enum = OpenSSL::ASN1::Enumerated(reason_code)
+      ext = OpenSSL::X509::Extension.new("CRLReason", enum)
+      revoked.add_extension(ext)
+      crl.add_revoked(revoked)
+    }
+    ef = OpenSSL::X509::ExtensionFactory.new
+    ef.issuer_certificate = issuer
+    ef.crl = crl
+    crlnum = OpenSSL::ASN1::Integer(serial)
+    crl.add_extension(OpenSSL::X509::Extension.new("crlNumber", crlnum))
+    extensions.each{|oid, value, critical|
+      crl.add_extension(ef.create_extension(oid, value, critical))
+    }
+    crl.sign(issuer_key, digest)
+    crl
+  end
+
+  def get_subject_key_id(cert)
+    asn1_cert = OpenSSL::ASN1.decode(cert)
+    tbscert   = asn1_cert.value[0]
+    pkinfo    = tbscert.value[6]
+    publickey = pkinfo.value[1]
+    pkvalue   = publickey.value
+    OpenSSL::Digest::SHA1.hexdigest(pkvalue).scan(/../).join(":").upcase
+  end
+
+  def silent
+    begin
+      back, $VERBOSE = $VERBOSE, nil
+      yield
+    ensure
+      $VERBOSE = back
+    end
+  end
+
+  class OpenSSL::TestCase < Test::Unit::TestCase
+    def setup
+      if ENV["OSSL_GC_STRESS"] == "1"
+        GC.stress = true
+      end
+    end
+
+    def teardown
+      if ENV["OSSL_GC_STRESS"] == "1"
+        GC.stress = false
+      end
+      # OpenSSL error stack must be empty
+      assert_equal([], OpenSSL.errors)
+    end
+  end
+
+  class OpenSSL::SSLTestCase < OpenSSL::TestCase
+    RUBY = EnvUtil.rubybin
+    ITERATIONS = ($0 == __FILE__) ? 100 : 10
+
+    def setup
+      super
+      @ca_key  = OpenSSL::TestUtils::TEST_KEY_RSA2048
+      @svr_key = OpenSSL::TestUtils::TEST_KEY_RSA1024
+      @cli_key = OpenSSL::TestUtils::TEST_KEY_DSA1024
+      @ca  = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=CA")
+      @svr = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=localhost")
+      @cli = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=localhost")
+      ca_exts = [
+        ["basicConstraints","CA:TRUE",true],
+        ["keyUsage","cRLSign,keyCertSign",true],
+      ]
+      ee_exts = [
+        ["keyUsage","keyEncipherment,digitalSignature",true],
+      ]
+      @ca_cert  = issue_cert(@ca, @ca_key, 1, ca_exts, nil, nil)
+      @svr_cert = issue_cert(@svr, @svr_key, 2, ee_exts, @ca_cert, @ca_key)
+      @cli_cert = issue_cert(@cli, @cli_key, 3, ee_exts, @ca_cert, @ca_key)
+      @server = nil
+    end
+
+    def issue_cert(*arg)
+      OpenSSL::TestUtils.issue_cert(*arg)
+    end
+
+    def issue_crl(*arg)
+      OpenSSL::TestUtils.issue_crl(*arg)
+    end
+
+    def readwrite_loop(ctx, ssl)
+      while line = ssl.gets
+        ssl.write(line)
+      end
+    rescue OpenSSL::SSL::SSLError
+    rescue IOError
+    ensure
+      ssl.close rescue nil
+    end
+
+    def server_loop(ctx, ssls, stop_pipe_r, ignore_listener_error, server_proc, threads)
+      loop do
+        ssl = nil
+        begin
+          readable, = IO.select([ssls, stop_pipe_r])
+          if readable.include? stop_pipe_r
+            return
+          end
+          ssl = ssls.accept
+        rescue OpenSSL::SSL::SSLError, Errno::ECONNRESET
+          if ignore_listener_error
+            retry
+          else
+            raise
+          end
+        end
+
+        th = Thread.start do
+          server_proc.call(ctx, ssl)
+        end
+        threads << th
+      end
+    rescue Errno::EBADF, IOError, Errno::EINVAL, Errno::ECONNABORTED, Errno::ENOTSOCK, Errno::ECONNRESET
+      if !ignore_listener_error
+        raise
+      end
+    end
+
+    def start_server(verify_mode: OpenSSL::SSL::VERIFY_NONE, start_immediately: true,
+                     ctx_proc: nil, server_proc: method(:readwrite_loop),
+                     ignore_listener_error: false, &block)
+      IO.pipe {|stop_pipe_r, stop_pipe_w|
+        store = OpenSSL::X509::Store.new
+        store.add_cert(@ca_cert)
+        store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT
+        ctx = OpenSSL::SSL::SSLContext.new
+        ctx.cert_store = store
+        ctx.cert = @svr_cert
+        ctx.key = @svr_key
+        ctx.tmp_dh_callback = proc { OpenSSL::TestUtils::TEST_KEY_DH1024 }
+        begin
+          ctx.ecdh_curves = "P-256"
+        rescue NotImplementedError
+        end
+        ctx.verify_mode = verify_mode
+        ctx_proc.call(ctx) if ctx_proc
+
+        Socket.do_not_reverse_lookup = true
+        tcps = nil
+        tcps = TCPServer.new("127.0.0.1", 0)
+        port = tcps.connect_address.ip_port
+
+        ssls = OpenSSL::SSL::SSLServer.new(tcps, ctx)
+        ssls.start_immediately = start_immediately
+
+        threads = []
+        begin
+          server = Thread.new do
+            begin
+              server_loop(ctx, ssls, stop_pipe_r, ignore_listener_error, server_proc, threads)
+            ensure
+              tcps.close
+            end
+          end
+          threads.unshift server
+
+          $stderr.printf("SSL server started: pid=%d port=%d\n", $$, port) if $DEBUG
+
+          client = Thread.new do
+            begin
+              block.call(server, port.to_i)
+            ensure
+              stop_pipe_w.close
+            end
+          end
+          threads.unshift client
+        ensure
+          assert_join_threads(threads)
+        end
+      }
+    end
+  end
+
+  class OpenSSL::PKeyTestCase < OpenSSL::TestCase
+    def check_component(base, test, keys)
+      keys.each { |comp|
+        assert_equal base.send(comp), test.send(comp)
+      }
+    end
+
+    def dup_public(key)
+      case key
+      when OpenSSL::PKey::RSA
+        rsa = OpenSSL::PKey::RSA.new
+        rsa.set_key(key.n, key.e, nil)
+        rsa
+      when OpenSSL::PKey::DSA
+        dsa = OpenSSL::PKey::DSA.new
+        dsa.set_pqg(key.p, key.q, key.g)
+        dsa.set_key(key.pub_key, nil)
+        dsa
+      when OpenSSL::PKey::DH
+        dh = OpenSSL::PKey::DH.new
+        dh.set_pqg(key.p, nil, key.g)
+        dh
+      else
+        if defined?(OpenSSL::PKey::EC) && OpenSSL::PKey::EC === key
+          ec = OpenSSL::PKey::EC.new(key.group)
+          ec.public_key = key.public_key
+          ec
+        else
+          raise "unknown key type"
+        end
+      end
+    end
+  end
+
+end if defined?(OpenSSL::OPENSSL_LIBRARY_VERSION) and
+  /\AOpenSSL +0\./ !~ OpenSSL::OPENSSL_LIBRARY_VERSION
diff --git a/tool/ruby-openssl-docker/Dockerfile b/tool/ruby-openssl-docker/Dockerfile
new file mode 100644
index 0000000..0bafbaa
--- /dev/null
+++ b/tool/ruby-openssl-docker/Dockerfile
@@ -0,0 +1,94 @@
+FROM ubuntu:14.04
+
+RUN apt-get update && apt-get install -y --no-install-recommends \
+  autoconf \
+  bison \
+  build-essential \
+  bzip2 \
+  ca-certificates \
+  cpio \
+  curl \
+  file \
+  git \
+  gzip \
+  libreadline-dev \
+  make \
+  patch \
+  pkg-config \
+  sed \
+  xz-utils \
+  zlib1g-dev
+
+# Supported OpenSSL versions: 1.0.1-
+RUN mkdir -p /build/openssl
+RUN curl -s https://www.openssl.org/source/openssl-1.0.0t.tar.gz | tar -C /build/openssl -xzf - && \
+    cd /build/openssl/openssl-1.0.0t && \
+    ./Configure \
+      --openssldir=/opt/openssl/openssl-1.0.0 \
+      shared linux-x86_64 && \
+    make && make install_sw
+
+RUN curl -s https://www.openssl.org/source/openssl-1.0.1u.tar.gz | tar -C /build/openssl -xzf - && \
+    cd /build/openssl/openssl-1.0.1u && \
+    ./Configure \
+      --openssldir=/opt/openssl/openssl-1.0.1 \
+      shared linux-x86_64 && \
+    make && make install_sw
+
+RUN curl -s https://www.openssl.org/source/openssl-1.0.2k.tar.gz | tar -C /build/openssl -xzf - && \
+    cd /build/openssl/openssl-1.0.2k && \
+    ./Configure \
+      --openssldir=/opt/openssl/openssl-1.0.2 \
+      shared linux-x86_64 && \
+    make && make install_sw
+
+RUN curl -s https://www.openssl.org/source/openssl-1.1.0e.tar.gz | tar -C /build/openssl -xzf - && \
+    cd /build/openssl/openssl-1.1.0e && \
+    ./Configure \
+      --prefix=/opt/openssl/openssl-1.1.0 \
+      enable-crypto-mdebug enable-crypto-mdebug-backtrace \
+      linux-x86_64 && \
+    make && make install_sw
+
+# Supported libressl versions: 2.3-
+RUN curl -s http://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-2.3.10.tar.gz | tar -C /build/openssl -xzf -
+RUN cd /build/openssl/libressl-2.3.10 && \
+    ./configure \
+      --prefix=/opt/openssl/libressl-2.3 && \
+    make && make install
+
+RUN curl -s http://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-2.4.5.tar.gz | tar -C /build/openssl -xzf -
+RUN cd /build/openssl/libressl-2.4.5 && \
+    ./configure \
+      --prefix=/opt/openssl/libressl-2.4 && \
+    make && make install
+
+RUN curl -s http://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-2.5.4.tar.gz | tar -C /build/openssl -xzf -
+RUN cd /build/openssl/libressl-2.5.4 && \
+    ./configure \
+      --prefix=/opt/openssl/libressl-2.5 && \
+    make && make install
+
+# Supported Ruby versions: 2.3-
+RUN mkdir -p /build/ruby
+RUN curl -s https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.4.tar.gz | tar -C /build/ruby -xzf - && \
+    cd /build/ruby/ruby-2.3.4 && \
+    autoconf && ./configure \
+      --without-openssl \
+      --prefix=/opt/ruby/ruby-2.3 \
+      --disable-install-doc && \
+    make && make install
+
+RUN curl -s https://cache.ruby-lang.org/pub/ruby/2.4/ruby-2.4.1.tar.gz | tar -C /build/ruby -xzf - && \
+    cd /build/ruby/ruby-2.4.1 && \
+    autoconf && ./configure \
+      --without-openssl \
+      --prefix=/opt/ruby/ruby-2.4 \
+      --disable-install-doc && \
+    make && make install
+
+ONBUILD ADD . /home/openssl/code
+ONBUILD WORKDIR /home/openssl/code
+
+COPY init.sh /home/openssl/init.sh
+ENTRYPOINT ["/home/openssl/init.sh"]
diff --git a/tool/ruby-openssl-docker/README.md b/tool/ruby-openssl-docker/README.md
new file mode 100644
index 0000000..93c75ed
--- /dev/null
+++ b/tool/ruby-openssl-docker/README.md
@@ -0,0 +1,6 @@
+# ruby-openssl-docker
+
+Docker image for testing. The image contains various binaries of supported
+versions of OpenSSL, LibreSSL, and Ruby.
+
+CONTRIBUTING.md on the top directory describes how to use the image.
diff --git a/tool/ruby-openssl-docker/init.sh b/tool/ruby-openssl-docker/init.sh
new file mode 100755
index 0000000..4d97e28
--- /dev/null
+++ b/tool/ruby-openssl-docker/init.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+if [[ "$RUBY_VERSION" = "" ]]
+then
+  RUBY_VERSION=ruby-2.4
+fi
+
+if [[ "$OPENSSL_VERSION" = "" ]]
+then
+  OPENSSL_VERSION=openssl-1.1.0
+fi
+
+echo "Using Ruby ${RUBY_VERSION} with OpenSSL ${OPENSSL_VERSION}."
+export PATH="/opt/ruby/${RUBY_VERSION}/bin:$PATH"
+export LD_LIBRARY_PATH="/opt/openssl/${OPENSSL_VERSION}/lib"
+export PKG_CONFIG_PATH="/opt/openssl/${OPENSSL_VERSION}/lib/pkgconfig"
+
+rake install_dependencies USE_HTTP_RUBYGEMS_ORG=1
+
+exec $*
diff --git a/tool/sync-with-trunk b/tool/sync-with-trunk
new file mode 100755
index 0000000..d721d2e
--- /dev/null
+++ b/tool/sync-with-trunk
@@ -0,0 +1,106 @@
+#!/bin/sh
+set -e
+
+# Pick changes from Ruby trunk and apply on this repository.
+# Note that Git >= 2.5 is required.
+
+sha1_to_rev() { git show -s --format=format:%B $1 | tail -n1 | grep -Po '(?<=@)[0-9]+'; }
+rev_to_sha1() { git log --format=format:%H -n1 --grep '^git-svn-id: .*@'$2' ' $1; }
+
+echo "#### Step 0. Fetch Ruby trunk"
+git remote | grep '^ruby$' >/dev/null || git remote add ruby https://github.com/ruby/ruby.git
+git fetch ruby
+
+BRANCH_EXTRACT=sync/extract
+echo "#### Step 1. Sync '$BRANCH_EXTRACT' with 'ruby/trunk'"
+[ "$(git branch --list $BRANCH_EXTRACT)" ] || git branch $BRANCH_EXTRACT ruby/trunk
+[ ! -d tmp/sync-extract ] && git worktree add tmp/sync-extract $BRANCH_EXTRACT
+(
+	cd tmp/sync-extract
+	git checkout $BRANCH_EXTRACT
+
+	if [ $(git rev-parse HEAD) = $(git rev-parse ruby/trunk) ]; then
+		filter_range=
+	else
+		old_head=$(git rev-parse HEAD)
+		echo "Updating '$BRANCH_EXTRACT'... HEAD was $old_head."
+		graftpoint="$(rev_to_sha1 ruby/trunk $(sha1_to_rev $old_head)) $old_head"
+		grep "^$graftpoint$" $(git rev-parse --git-common-dir)/info/grafts >/dev/null 2>&1 ||
+			echo "$graftpoint" >> $(git rev-parse --git-common-dir)/info/grafts
+		git reset -q --hard ruby/trunk
+		filter_range=$old_head..$BRANCH_EXTRACT
+	fi
+
+	echo "## Remove unrelated commits"
+	git filter-branch -f --prune-empty --index-filter '
+		git rm --cached -qr --ignore-unmatch . &&
+		git reset -q $GIT_COMMIT -- ext/openssl test/openssl sample/openssl &&
+		git rm --cached -q --ignore-unmatch ext/openssl/depend ext/openssl/openssl.gemspec
+	' -- $filter_range ||:
+
+	echo "## Adjust path"
+	git filter-branch -f --prune-empty --index-filter '
+		git ls-files --stage | \
+		    sed "s:\ttest/openssl:\ttest:" | \
+		    sed "s:\text/openssl/lib:\tlib:" | \
+		    sed "s:\text/openssl/sample:\tsample/openssl:" | \
+		    sed "s:\tsample/openssl:\tsample:" | \
+		    sed "s:\text/openssl/History.md:\tHistory.md:" | \
+		    GIT_INDEX_FILE=$GIT_INDEX_FILE.new git update-index --index-info &&
+		mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"
+		:
+	' -- $filter_range ||:
+
+	echo "## Fix author/committer email"
+	git filter-branch -f --env-filter '
+		export GIT_AUTHOR_EMAIL=${GIT_AUTHOR_EMAIL/b2dd03c8-39d4-4d8f-98ff-823fe69b080e/ruby-lang.org}
+		export GIT_COMMITTER_EMAIL=${GIT_COMMITTER_EMAIL/b2dd03c8-39d4-4d8f-98ff-823fe69b080e/ruby-lang.org}
+	' -- $filter_range ||:
+
+	[ "$graftpoint" ] &&
+		sed -ie "/^$graftpoint$/d" $(git rev-parse --git-common-dir)/info/grafts
+)
+
+LAST_SYNC_COMMIT=$(git log --format=format:%H -n1 --grep '^Sync-with-trunk: r')
+LAST_SYNC_REV=$(git show --format=format:%B $LAST_SYNC_COMMIT | grep -Po '(?<=^Sync-with-trunk: r)[0-9]+$' | tail -n1)
+NEXT_SYNC_REV=$(sha1_to_rev $BRANCH_EXTRACT)
+
+[ "$LAST_SYNC_REV" = "$NEXT_SYNC_REV" ] && (
+	echo "No changes since last sync; aborting."
+	exit 1
+)
+
+BRANCH_MERGE=sync/merge-r$NEXT_SYNC_REV
+echo "#### Step 2. Rebase '$BRANCH_EXTRACT' on the last sync commit"
+[ "$(git branch --list $BRANCH_MERGE)" ] || git branch $BRANCH_MERGE $BRANCH_EXTRACT
+[ ! -d tmp/sync-merge ] && git worktree add tmp/sync-merge $BRANCH_MERGE
+(
+	cd tmp/sync-merge
+	git checkout $BRANCH_MERGE
+	git reset -q --hard $BRANCH_EXTRACT
+	git rebase --onto $LAST_SYNC_COMMIT $(rev_to_sha1 $BRANCH_EXTRACT $LAST_SYNC_REV) $BRANCH_MERGE
+)
+
+echo "#### Step 3. Merge '$BRANCH_MERGE' into '$(git rev-parse --abbrev-ref HEAD)'"
+commit_message=$(
+	commits=$(git log --oneline --format='%H %<(61,trunc)%s' $LAST_SYNC_COMMIT..$BRANCH_MERGE)
+	echo "Merge changes from Ruby trunk r$LAST_SYNC_REV..r$NEXT_SYNC_REV"
+	echo ""
+	echo "* ruby-trunk r$LAST_SYNC_REV..r$NEXT_SYNC_REV: ($(echo "$commits" | wc -l) commits)"
+	echo "$commits" | while read line; do
+		sha1=$(echo "$line" | cut -f1 -d' ')
+		cmsg=$(echo "$line" | cut -f2- -d' ')
+		echo "  (r$(sha1_to_rev $sha1)) $cmsg"
+	done
+
+	echo ""
+	echo "Sync-with-trunk: r$NEXT_SYNC_REV"
+)
+if git merge --no-ff --no-commit $BRANCH_MERGE; then
+	git commit -m "$commit_message"
+else
+	echo "Merge failed; fix conflict and commit with message:"
+	echo ""
+	echo "$commit_message"
+	exit 1
+fi
diff --git a/tool/update-gh-pages b/tool/update-gh-pages
new file mode 100755
index 0000000..8eb546a
--- /dev/null
+++ b/tool/update-gh-pages
@@ -0,0 +1,22 @@
+#!/bin/sh
+set -e
+
+# Generates RDoc HTML and update gh-pages branch.
+
+HEAD_DESCRIPTION=$(git describe --dirty --always --abbrev=12)
+
+[ -d html ] &&
+	rm -r html
+rake rdoc
+
+[ ! -d tmp/gh-pages ] &&
+	git worktree add tmp/gh-pages gh-pages
+(
+	cd tmp/gh-pages
+
+	git rm -r .
+	cp -r ../../html/* .
+	rm created.rid js/*.gz # to avoid unnecessary change :x
+	git add .
+	git commit -m "Sync with $HEAD_DESCRIPTION"
+)

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-ruby-extras/ruby-openssl.git



More information about the Pkg-ruby-extras-commits mailing list