Repository: ruby/openssl Branch: master Commit: 8a7c9b8f2869 Files: 139 Total size: 1.3 MB Directory structure: gitextract_bnbciiwa/ ├── .git-blame-ignore-revs ├── .github/ │ ├── dependabot.yml │ └── workflows/ │ ├── github-pages.yml │ ├── push_gem.yml │ ├── sync-ruby.yml │ └── test.yml ├── .gitignore ├── BSDL ├── CONTRIBUTING.md ├── COPYING ├── Gemfile ├── History.md ├── README.md ├── Rakefile ├── ext/ │ └── openssl/ │ ├── extconf.rb │ ├── openssl_missing.h │ ├── ossl.c │ ├── ossl.h │ ├── ossl_asn1.c │ ├── ossl_asn1.h │ ├── ossl_bio.c │ ├── ossl_bio.h │ ├── ossl_bn.c │ ├── ossl_bn.h │ ├── ossl_cipher.c │ ├── ossl_cipher.h │ ├── ossl_config.c │ ├── ossl_config.h │ ├── ossl_digest.c │ ├── ossl_digest.h │ ├── ossl_engine.c │ ├── ossl_engine.h │ ├── ossl_hmac.c │ ├── ossl_hmac.h │ ├── ossl_kdf.c │ ├── ossl_kdf.h │ ├── ossl_ns_spki.c │ ├── ossl_ns_spki.h │ ├── ossl_ocsp.c │ ├── ossl_ocsp.h │ ├── ossl_pkcs12.c │ ├── ossl_pkcs12.h │ ├── ossl_pkcs7.c │ ├── ossl_pkcs7.h │ ├── ossl_pkey.c │ ├── ossl_pkey.h │ ├── ossl_pkey_dh.c │ ├── ossl_pkey_dsa.c │ ├── ossl_pkey_ec.c │ ├── ossl_pkey_rsa.c │ ├── ossl_provider.c │ ├── ossl_provider.h │ ├── ossl_rand.c │ ├── ossl_rand.h │ ├── ossl_ssl.c │ ├── ossl_ssl.h │ ├── ossl_ssl_session.c │ ├── ossl_ts.c │ ├── ossl_ts.h │ ├── ossl_x509.c │ ├── ossl_x509.h │ ├── ossl_x509attr.c │ ├── ossl_x509cert.c │ ├── ossl_x509crl.c │ ├── ossl_x509ext.c │ ├── ossl_x509name.c │ ├── ossl_x509req.c │ ├── ossl_x509revoked.c │ └── ossl_x509store.c ├── lib/ │ ├── openssl/ │ │ ├── bn.rb │ │ ├── buffering.rb │ │ ├── cipher.rb │ │ ├── digest.rb │ │ ├── hmac.rb │ │ ├── marshal.rb │ │ ├── pkcs5.rb │ │ ├── pkey.rb │ │ ├── ssl.rb │ │ ├── version.rb │ │ └── x509.rb │ └── openssl.rb ├── openssl.gemspec ├── sample/ │ ├── c_rehash.rb │ ├── cert2text.rb │ ├── certstore.rb │ ├── cipher.rb │ ├── crlstore.rb │ ├── echo_cli.rb │ ├── echo_svr.rb │ ├── gen_csr.rb │ ├── smime_read.rb │ ├── smime_write.rb │ └── wget.rb ├── test/ │ └── openssl/ │ ├── fixtures/ │ │ └── pkey/ │ │ ├── dh-1.pem │ │ ├── dh2048_ffdhe2048.pem │ │ ├── dsa2048.pem │ │ ├── mldsa65-1.pem │ │ ├── mldsa65-2.pem │ │ ├── p256.pem │ │ ├── rsa-1.pem │ │ ├── rsa-2.pem │ │ ├── rsa-3.pem │ │ └── rsa2048.pem │ ├── test_asn1.rb │ ├── test_bn.rb │ ├── test_buffering.rb │ ├── test_cipher.rb │ ├── test_config.rb │ ├── test_digest.rb │ ├── test_engine.rb │ ├── test_fips.rb │ ├── test_hmac.rb │ ├── test_kdf.rb │ ├── test_ns_spki.rb │ ├── test_ocsp.rb │ ├── test_ossl.rb │ ├── test_pair.rb │ ├── test_pkcs12.rb │ ├── test_pkcs7.rb │ ├── test_pkey.rb │ ├── test_pkey_dh.rb │ ├── test_pkey_dsa.rb │ ├── test_pkey_ec.rb │ ├── test_pkey_rsa.rb │ ├── test_provider.rb │ ├── test_random.rb │ ├── test_ssl.rb │ ├── test_ssl_session.rb │ ├── test_ts.rb │ ├── test_x509attr.rb │ ├── test_x509cert.rb │ ├── test_x509crl.rb │ ├── test_x509ext.rb │ ├── test_x509name.rb │ ├── test_x509req.rb │ ├── test_x509store.rb │ ├── ut_eof.rb │ └── utils.rb └── tool/ └── openssl_fips.cnf.tmpl ================================================ FILE CONTENTS ================================================ ================================================ FILE: .git-blame-ignore-revs ================================================ # This is a file used by GitHub to ignore the following commits on `git blame`. # # You can also do the same thing in your local repository with: # $ git config --local blame.ignoreRevsFile .git-blame-ignore-revs # Expand tabs in C source files 4d6214f50758ea1738bc45e25776ba852321f513 ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: 'github-actions' directory: '/' schedule: interval: 'weekly' ================================================ FILE: .github/workflows/github-pages.yml ================================================ name: GitHub Pages on: push: branches: - master workflow_dispatch: jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: ruby/setup-ruby@v1 with: ruby-version: ruby bundler-cache: true # 'bundle install' and cache gems - run: bundle exec rake rdoc - name: Upload GitHub Pages artifact uses: actions/upload-pages-artifact@v5 with: path: html deploy: needs: build permissions: pages: write id-token: write environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v5 ================================================ FILE: .github/workflows/push_gem.yml ================================================ name: Publish gem to rubygems.org on: push: tags: - 'v*' permissions: contents: read jobs: push: if: github.repository == 'ruby/openssl' runs-on: ubuntu-latest environment: name: rubygems.org url: https://rubygems.org/gems/openssl permissions: contents: write id-token: write strategy: matrix: ruby: [ 'ruby', 'jruby' ] steps: - name: Harden Runner uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 with: egress-policy: audit - uses: actions/checkout@v6 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: bundler-cache: true ruby-version: ${{ matrix.ruby }} - name: Publish to RubyGems uses: rubygems/release-gem@v1 - name: Create GitHub release run: | tag_name="$(git describe --tags --abbrev=0)" gh release create "${tag_name}" --verify-tag --draft --generate-notes pkg/*.gem env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} if: matrix.ruby == 'ruby' ================================================ FILE: .github/workflows/sync-ruby.yml ================================================ name: Sync ruby on: push: branches: [master] jobs: sync: name: Sync ruby runs-on: ubuntu-latest if: ${{ github.repository_owner == 'ruby' }} steps: - uses: actions/checkout@v6 - name: Create GitHub App token id: app-token uses: actions/create-github-app-token@v3 with: app-id: 2060836 private-key: ${{ secrets.RUBY_SYNC_DEFAULT_GEMS_PRIVATE_KEY }} owner: ruby repositories: ruby - name: Sync to ruby/ruby uses: convictional/trigger-workflow-and-wait@v1.6.5 with: owner: ruby repo: ruby workflow_file_name: sync_default_gems.yml github_token: ${{ steps.app-token.outputs.token }} ref: master client_payload: | {"gem":"${{ github.event.repository.name }}","before":"${{ github.event.before }}","after":"${{ github.event.after }}"} propagate_failure: true wait_interval: 10 ================================================ FILE: .github/workflows/test.yml ================================================ name: CI on: [push, pull_request, workflow_dispatch] jobs: ruby-versions: uses: ruby/actions/.github/workflows/ruby_versions.yml@master with: engine: cruby-truffleruby min_version: 2.7 test: needs: ruby-versions name: >- ${{ matrix.os }} ${{ matrix.ruby }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ ubuntu-latest, macos-latest, windows-latest ] ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} exclude: - { os: windows-latest, ruby: truffleruby } - { os: windows-latest, ruby: truffleruby-head } include: - { os: windows-latest, ruby: ucrt } - { os: windows-latest, ruby: mswin } steps: &test-steps - name: repo checkout uses: actions/checkout@v6 - name: load ruby uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} bundler-cache: true # `bundle install` and cache if: ${{ !endsWith(matrix.os, 'ppc64le') && !endsWith(matrix.os, 's390x') }} - name: load ruby from deb package run: | sudo apt update sudo apt install ruby-full bundler sudo bundle install --jobs $(nproc) if: ${{ endsWith(matrix.os, 'ppc64le') || endsWith(matrix.os, 's390x') }} # See https://github.com/oneclick/rubyinstaller2/issues/60 # The builtin DLLs are preferred over the mingw-w64/vcpkg DLLs. This is a # temporary workaround until they update the DLLs to OpenSSL 3.5.x. - name: Update RI2/mswin builtin DLLs run: | $dst = "$((Get-Item (Get-Command ruby).Definition).DirectoryName)\ruby_builtin_dlls" if ("${{ matrix.ruby }}" -eq "mswin") { $src = "C:\vcpkg\installed\x64-windows\bin" } else { $src = "$((Get-Item (Get-Command ruby).Definition).DirectoryName)\..\msys64\ucrt64\bin" } Copy-Item "$src\libcrypto-3-x64.dll", "$src\libssl-3-x64.dll" $dst if: ${{ matrix.os == 'windows-latest' && (matrix.ruby == '3.2' || matrix.ruby == '3.3' || matrix.ruby == 'mswin') }} # Enable the verbose option in mkmf.rb to print the compiling commands. - name: enable mkmf verbose run: echo "MAKEFLAGS=V=1" >> $GITHUB_ENV if: runner.os == 'Linux' || runner.os == 'macOS' - name: set flags to check compiler warnings run: echo "RUBY_OPENSSL_EXTCFLAGS=-Werror" >> $GITHUB_ENV if: ${{ !matrix.skip-warnings }} - name: rake compile run: bundle exec rake debug_compiler compile - name: rake debug run: bundle exec rake debug - name: rake test run: bundle exec rake test TESTOPTS="-v --no-show-detail-immediately" OSSL_TEST_ALL=1 timeout-minutes: 5 test-ibm: if: github.repository == 'ruby/openssl' name: ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: include: - os: ubuntu-24.04-ppc64le - os: ubuntu-24.04-s390x steps: *test-steps test-openssls: name: >- ${{ matrix.openssl }} ${{ matrix.name-extra }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: name-extra: [ '' ] openssl: # https://openssl-library.org/source/ - openssl-1.1.1w # EOL 2023-09-11, still used by RHEL 8 and Ubuntu 20.04 - openssl-3.0.20 # Supported until 2026-09-07 (LTS) - openssl-3.1.8 # EOL 2025-03-14 - openssl-3.2.6 # EOL 2025-11-23 - openssl-3.3.7 # EOL 2026-04-09 - openssl-3.4.5 # Supported until 2026-10-22 - openssl-3.5.6 # Supported until 2030-04-08 (LTS) - openssl-3.6.2 # Supported until 2026-11-01 - openssl-4.0.0 # Supported until 2027-05-14 - openssl-master # http://www.libressl.org/releases.html - libressl-3.9.2 # EOL 2025-04-05 - libressl-4.0.1 # EOL 2025-10-08 - libressl-4.1.2 # Supported until 2026-04-28 - libressl-4.2.1 # Supported until 2026-10-22 # https://github.com/aws/aws-lc/tags - aws-lc-latest include: - { name-extra: 'without legacy provider', openssl: openssl-4.0.0, append-configure: 'no-legacy' } - { openssl: aws-lc-latest, skip-warnings: true } steps: - name: repo checkout uses: actions/checkout@v6 - id: cache-openssl uses: actions/cache@v5 with: path: ~/openssl key: openssl-${{ runner.os }}-${{ matrix.openssl }}-${{ matrix.append-configure || 'default' }} if: matrix.openssl != 'openssl-master' && matrix.openssl != 'libressl-master' && matrix.openssl != 'aws-lc-latest' - name: Compile OpenSSL library if: steps.cache-openssl.outputs.cache-hit != 'true' run: | # Enable Bash debugging option temporarily for debugging use. set -x mkdir -p tmp/build-openssl && cd tmp/build-openssl case ${{ matrix.openssl }} in openssl-1.*) OPENSSL_COMMIT=$(echo ${{ matrix.openssl }} | sed -e 's/^openssl-/OpenSSL_/' | sed -e 's/\./_/g') git clone -b $OPENSSL_COMMIT --depth 1 https://github.com/openssl/openssl.git . echo "Git commit: $(git rev-parse HEAD)" # shared is required for 1.0.x. ./Configure --prefix=$HOME/openssl --libdir=lib shared linux-x86_64 make depend && make -j4 && make install_sw ;; openssl-*) OPENSSL_COMMIT=${{ matrix.openssl == 'openssl-master' && 'master' || matrix.openssl }} git clone -b $OPENSSL_COMMIT --depth 1 https://github.com/openssl/openssl.git . echo "Git commit: $(git rev-parse HEAD)" ./Configure --prefix=$HOME/openssl --libdir=lib enable-fips no-tests ${{ matrix.append-configure }} make -j4 && make install_sw && make install_fips ;; libressl-*) curl -L https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/${{ matrix.openssl }}.tar.gz | \ tar xzf - --strip-components=1 ./configure --prefix=$HOME/openssl make -j4 && make install ;; aws-lc-*) git clone https://github.com/aws/aws-lc.git . AWS_LC_RELEASE=$(git tag --sort=-creatordate --list "v*" | head -1) git checkout $AWS_LC_RELEASE cmake -DCMAKE_INSTALL_PREFIX=$HOME/openssl -DCMAKE_INSTALL_LIBDIR=lib make -j4 && make install ;; *) false ;; esac - name: load ruby uses: ruby/setup-ruby@v1 with: ruby-version: '3.0' bundler-cache: true - name: enable mkmf verbose run: echo "MAKEFLAGS=V=1" >> $GITHUB_ENV - name: set flags to check compiler warnings run: echo "RUBY_OPENSSL_EXTCFLAGS=-Werror" >> $GITHUB_ENV if: ${{ !matrix.skip-warnings }} - name: rake compile run: bundle exec rake debug_compiler compile -- --with-openssl-dir=$HOME/openssl - name: rake debug run: bundle exec rake debug - name: rake test run: bundle exec rake test TESTOPTS="-v --no-show-detail-immediately" OSSL_TEST_ALL=1 timeout-minutes: 5 # Run only the passing tests on the FIPS module as a temporary workaround. # TODO Fix other tests, and run all the tests on FIPS module. - name: rake test_fips run: | sed -e "s|OPENSSL_DIR|$HOME/openssl|" tool/openssl_fips.cnf.tmpl > tmp/openssl_fips.cnf export OPENSSL_CONF=$(pwd)/tmp/openssl_fips.cnf bundle exec rake debug bundle exec rake test_fips TESTOPTS="-v --no-show-detail-immediately" OSSL_TEST_ALL=1 timeout-minutes: 5 if: ${{ startsWith(matrix.openssl, 'openssl-3') || matrix.openssl == 'openssl-master' }} ================================================ FILE: .gitignore ================================================ /.bundle /Gemfile.lock /doc/ /pkg/ /tmp/ /html/ *.bundle *.so *.o ext/openssl/mkmf.log ext/openssl/Makefile ext/openssl/extconf.h ================================================ FILE: BSDL ================================================ 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. ================================================ FILE: CONTRIBUTING.md ================================================ # 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"][Ruby Security]. When reporting a bug, please make sure you include: * Ruby version (`ruby -v`) * `openssl` gem version (`gem list openssl` and `OpenSSL::VERSION`) * OpenSSL library version (`OpenSSL::OPENSSL_VERSION`) * A sample file that illustrates the problem or link to the repository or gem that is associated with the bug. There are 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][Issues]. ## Submitting patches Patches are also very welcome! Please submit a [pull request][Compare changes] with your changes. Make sure that your branch does: * Have good commit messages * Follow Ruby's coding style ([Developer-How-To][Ruby Developer-How-To]) * Pass the test suite successfully (see "Testing") ## Testing We have a test suite! Test cases are located under the [`test/openssl`][GitHub test/openssl] directory. You can run it with the following three commands: ``` $ bundle install # installs rake-compiler, test-unit, ... $ bundle exec rake compile $ bundle exec rake test ``` ### With different versions of OpenSSL Ruby OpenSSL supports various versions of the OpenSSL library. The test suite needs to pass on all supported combinations. If you want to test, debug, report an issue, or contribute to the Ruby OpenSSL or [the OpenSSL project][OpenSSL] in the non-FIPS or the [FIPS][OpenSSL README-FIPS] case, compiling OpenSSL from the source by yourself is a good practice. The following steps are tested in Linux and GCC environment. You can adjust the commands in the steps for a different environment. To download the OpenSSL source from the Git repository, you can run the following commands: ``` $ git clone https://github.com/openssl/openssl.git $ cd openssl ``` You see the `master` branch used as a development branch. Testing against the latest OpenSSL master branch is a good practice to report an issue to the OpenSSL project. ``` $ git branch | grep '^*' * master ``` If you test against the latest stable branch, you can run the following command. In this example, the `openssl-3.1` branch is the stable branch of OpenSSL 3.1 series. ``` $ git checkout openssl-3.1 ``` To configure OpenSSL, you can run the following commands. In this example, we use the `OPENSSL_DIR` environment variable to specify the OpenSSL installed directory for convenience. Including the commit hash in the directory name is a good practice. ``` $ git rev-parse --short HEAD 0bf18140f4 $ OPENSSL_DIR=$HOME/.openssl/openssl-fips-debug-0bf18140f4 ``` The following configuration options are useful in this case. You can check [OpenSSL installation document][OpenSSL INSTALL] for details. * `enable-fips`: Add an option to run with the OpenSSL FIPS module. * `enable-trace`: Add an option to enabling tracing log. You can trace logs by implementing a code. See the man page [OSSL_TRACE(3)][OpenSSL OSSL_TRACE] for details. * compiler flags * `-Wl,-rpath,$(LIBRPATH)`: Set the runtime shared library path to run the `openssl` command without the `LD_LIBRARY_PATH`. You can check [this document][OpenSSL NOTES-UNIX] for details. * `-O0 -g3 -ggdb3 -gdwarf-5`: You can set debugging compiler flags. ``` $ ./Configure \ --prefix=$OPENSSL_DIR \ --libdir=lib \ enable-fips \ enable-trace \ '-Wl,-rpath,$(LIBRPATH)' \ -O0 -g3 -ggdb3 -gdwarf-5 $ make -j4 $ make install ``` To print installed OpenSSL version, you can run the following command: ``` $ $OPENSSL_DIR/bin/openssl version OpenSSL 3.2.0-alpha3-dev (Library: OpenSSL 3.2.0-alpha3-dev ) ``` Change the current working directory into Ruby OpenSSL's source directory. To compile Ruby OpenSSL, you can run the following commands: Similarly to when installing `openssl` gem via the `gem` command, you can pass a `--with-openssl-dir` argument to `rake compile` to specify the OpenSSL library to build against. * `MAKEFLAGS="V=1"`: Enable the compiler command lines to print in the log. * `RUBY_OPENSSL_EXTCFLAGS`: Set extra compiler flags to compile Ruby OpenSSL. ``` $ bundle exec rake clean $ MAKEFLAGS="V=1" \ RUBY_OPENSSL_EXTCFLAGS="-O0 -g3 -ggdb3 -gdwarf-5" \ bundle exec rake compile -- --with-openssl-dir=$OPENSSL_DIR ``` #### Testing normally in non-FIPS case To test Ruby OpenSSL, you can run the following command: ``` $ bundle exec rake test ``` #### Testing in FIPS case To use OpenSSL 3.0 or later versions in a FIPS-approved manner, you must load the `fips` and `base` providers, and also use the property query `fips=yes`. The property query is used when fetching cryptographic algorithm implementations. This must be done at the startup of a process to avoid implicitly loading the `default` provider which has the non-FIPS cryptographic algorithm implementations. See also the man page [fips_module(7)][OpenSSL fips_module]. You can set this in your OpenSSL configuration file by either appropriately modifying the default OpenSSL configuration file located at `OpenSSL::Config::DEFAULT_CONFIG_FILE` or temporarily overriding it with the `OPENSSL_CONF` environment variable. In this example, we explain on the latter way. You can create a OpenSSL FIPS config `openssl_fips.cnf` file based on the `openssl_fips.cnf.tmpl` file in this repository, and replacing the placeholder `OPENSSL_DIR` with your OpenSSL installed directory. ``` $ sed -e "s|OPENSSL_DIR|$OPENSSL_DIR|" tool/openssl_fips.cnf.tmpl | \ tee $OPENSSL_DIR/ssl/openssl_fips.cnf ``` You can see the base and fips providers by running the following command if you setup the OpenSSL FIPS config file properly. ``` $ OPENSSL_CONF=$OPENSSL_DIR/ssl/openssl_fips.cnf \ $OPENSSL_DIR/bin/openssl list -providers Providers: base name: OpenSSL Base Provider version: 3.2.0 status: active fips name: OpenSSL FIPS Provider version: 3.2.0 status: active ``` You can run the current tests in the FIPS module case used in the GitHub Actions file `test.yml` explained in a later sentence. ``` $ OPENSSL_CONF=$OPENSSL_DIR/ssl/openssl_fips.cnf \ bundle exec rake test_fips ``` You can also run the all the tests in the FIPS module case. You see many failures. We are working in progress to fix the failures. Your contribution is welcome. ``` $ OPENSSL_CONF=$OPENSSL_DIR/ssl/openssl_fips.cnf \ TEST_RUBY_OPENSSL_FIPS_ENABLED=true \ bundle exec rake test ``` The GitHub Actions workflow file [`test.yml`][GitHub test.yml] contains useful information for building OpenSSL/LibreSSL and testing against them. ## Debugging You can use the `OpenSSL.debug = true` to print additional error strings. ## 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@ruby-lang.org. Please see [Security][Ruby 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 [Issues]: https://github.com/ruby/openssl/issues [Compare changes]: https://github.com/ruby/openssl/compare [GitHub test/openssl]: https://github.com/ruby/openssl/tree/master/test/openssl [GitHub test.yml]: https://github.com/ruby/openssl/tree/master/.github/workflows/test.yml [Ruby Developer-How-To]: https://github.com/ruby/ruby/wiki/Developer-How-To [Ruby Security]: https://www.ruby-lang.org/en/security/ [HackerOne]: https://hackerone.com/ruby [OpenSSL]: https://www.openssl.org/ [OpenSSL INSTALL]: https://github.com/openssl/openssl/blob/master/INSTALL.md [OpenSSL README-FIPS]: https://github.com/openssl/openssl/blob/master/README-FIPS.md [OpenSSL NOTES-UNIX]: https://github.com/openssl/openssl/blob/master/NOTES-UNIX.md [OpenSSL OSSL_TRACE]: https://www.openssl.org/docs/manmaster/man3/OSSL_TRACE.html [OpenSSL fips_module]: https://www.openssl.org/docs/manmaster/man7/fips_module.html ================================================ FILE: COPYING ================================================ Ruby is copyrighted free software by Yukihiro Matsumoto . 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. ================================================ FILE: Gemfile ================================================ source "https://rubygems.org" gem "rake" gem "rake-compiler" gem "test-unit", "~> 3.0", ">= 3.4.6" gem "test-unit-ruby-core" gem "prime" gem "rdoc" # Pulled in by test-unit. power_assert 3.x requires Ruby >= 3.1 gem "power_assert", "< 3" if RUBY_VERSION < "3.1" ================================================ FILE: History.md ================================================ Version 4.0.1 ============= Notable changes --------------- * Add `sync_close` keyword argument to `OpenSSL::SSL::SSLSocket.new` as a short-hand for setting `sync_close` attribute on the created `SSLSocket` instance. [[GitHub #955]](https://github.com/ruby/openssl/issues/955) [[GitHub #996]](https://github.com/ruby/openssl/pull/996) Bug fixes --------- * Fix uninitialized variables in `OpenSSL::OCSP::BasicResponse#status`. [[GitHub #1004]](https://github.com/ruby/openssl/pull/1004) Version 4.0.0 ============= Compatibility ------------- * Ruby >= 2.7 * OpenSSL >= 1.1.1, LibreSSL >= 3.9, and AWS-LC 1.66.0 - Removed support for OpenSSL 1.0.2-1.1.0 and LibreSSL 3.1-3.8. [[GitHub #835]](https://github.com/ruby/openssl/issues/835) - Added support for AWS-LC. [[GitHub #833]](https://github.com/ruby/openssl/issues/833) Notable changes --------------- * `OpenSSL::SSL` - Reduce overhead when writing to `OpenSSL::SSL::SSLSocket`. `#syswrite` no longer creates a temporary String object. [[GitHub #831]](https://github.com/ruby/openssl/pull/831) - Make `OpenSSL::SSL::SSLContext#min_version=` and `#max_version=` wrap the corresponding OpenSSL APIs directly, and remove the fallback to SSL options. [[GitHub #849]](https://github.com/ruby/openssl/pull/849) - Add `OpenSSL::SSL::SSLContext#sigalgs=` and `#client_sigalgs=` for specifying signature algorithms to use for connections. [[GitHub #895]](https://github.com/ruby/openssl/pull/895) - Rename `OpenSSL::SSL::SSLContext#ecdh_curves=` to `#groups=` following the underlying OpenSSL API rename. This method is no longer specific to ECDHE. The old method remains as an alias. [[GitHub #900]](https://github.com/ruby/openssl/pull/900) - Add `OpenSSL::SSL::SSLSocket#sigalg`, `#peer_sigalg`, and `#group` for getting the signature algorithm and the key agreement group used in the current connection. [[GitHub #908]](https://github.com/ruby/openssl/pull/908) - Enable `SSL_CTX_set_dh_auto()` for servers by default. [[GitHub #924]](https://github.com/ruby/openssl/pull/924) - Improve Ractor compatibility. Note that the internal-use constant `OpenSSL::SSL::SSLContext::DEFAULT_PARAMS` is now frozen. [[GitHub #925]](https://github.com/ruby/openssl/pull/925) * `OpenSSL::PKey` - Remove `OpenSSL::PKey::EC::Point#mul` support with array arguments. The underlying OpenSSL API has been removed, and the method has been deprecated since ruby/openssl v3.0.0. [[GitHub #843]](https://github.com/ruby/openssl/pull/843) - `OpenSSL::PKey::{RSA,DSA,DH}#params` uses `nil` to indicate missing fields instead of the number `0`. [[GitHub #774]](https://github.com/ruby/openssl/pull/774) - Unify `OpenSSL::PKey::PKeyError` classes. The former subclasses `OpenSSL::PKey::DHError`, `OpenSSL::PKey::DSAError`, `OpenSSL::PKey::ECError`, and `OpenSSL::PKey::RSAError` have been merged into a single class. [[GitHub #929]](https://github.com/ruby/openssl/pull/929) * `OpenSSL::Cipher` - `OpenSSL::Cipher#encrypt` and `#decrypt` no longer accept arguments. Passing passwords has been deprecated since Ruby 1.8.2 (released in 2004). [[GitHub #887]](https://github.com/ruby/openssl/pull/887) - `OpenSSL::Cipher#final` raises `OpenSSL::Cipher::AuthTagError` when the integrity check fails for AEAD ciphers. `OpenSSL::Cipher::AuthTagError` is a new subclass of `OpenSSL::Cipher::CipherError`, which was previously raised. [[GitHub #939]](https://github.com/ruby/openssl/pull/939) - `OpenSSL::Cipher.new` now raises `OpenSSL::Cipher::CipherError` instead of `RuntimeError` when OpenSSL does not recognize the algorithm. [[GitHub #958]](https://github.com/ruby/openssl/pull/958) - Add support for "fetched" cipher algorithms with OpenSSL 3.0 or later. [[GitHub #958]](https://github.com/ruby/openssl/pull/958) * `OpenSSL::Digest` - `OpenSSL::Digest.new` now raises `OpenSSL::Digest::DigestError` instead of `RuntimeError` when OpenSSL does not recognize the algorithm. [[GitHub #958]](https://github.com/ruby/openssl/pull/958) - Add support for "fetched" digest algorithms with OpenSSL 3.0 or later. [[GitHub #958]](https://github.com/ruby/openssl/pull/958) * `OpenSSL::ASN1.decode` now assumes a 1950-2049 year range for `UTCTime` according to RFC 5280. It previously used a 1969-2068 range. The encoder has always used the 1950-2049 range. [[GitHub #909]](https://github.com/ruby/openssl/pull/909) * `OpenSSL::OpenSSLError`, the base class for all ruby/openssl errors, carry an additional attribute `#errors` to keep the content of OpenSSL's error queue. Also, add `#detailed_message` for Ruby 3.2 or later. [[GitHub #976]](https://github.com/ruby/openssl/pull/976) * `OpenSSL::PKCS7.new` raises `OpenSSL::PKCS7::PKCS7Error` instead of `ArgumentError` on error to be consistent with other constructors. [[GitHub #983]](https://github.com/ruby/openssl/pull/983) Version 3.3.2 ============= Merged changes in 3.1.3 and 3.2.3. Version 3.3.1 ============= Merged changes in 3.1.2 and 3.2.2. Version 3.3.0 ============= Compatibility ------------- * Ruby version: 2.7 or later * OpenSSL version: OpenSSL 1.0.2 or later, and LibreSSL 3.1 or later Notable changes --------------- * `OpenSSL::SSL` - `OpenSSL::SSL::SSLSocket#set_params` no longer sets `#min_version=` to TLS 1.0 except when OpenSSL 1.0.2 is used. This has been done to disable SSL 3.0, which is not supported by default in OpenSSL 1.1.0 or later, or in LibreSSL. This lets it respect the system default if the system-wide configuration file specifies a higher minimum protocol version. [[GitHub #710]](https://github.com/ruby/openssl/pull/710) - `OpenSSL::SSL::SSLSocket.new` no longer enables the `OpenSSL::SSL::OP_ALL` SSL options by default and follows the system default. [[GitHub #767]](https://github.com/ruby/openssl/pull/767) - Add the following IO methods to `OpenSSL::SSL::SSLSocket`, which will pass along to the underlying socket: `#local_address`, `#remote_address`, `#close_on_exec=`, `#close_on_exec?`, `#wait`, `#wait_readable`, and `#wait_writable`. [[GitHub #708]](https://github.com/ruby/openssl/pull/708) - Update `OpenSSL::SSL::SSLSocket#gets` to take the `chomp` keyword argument. [[GitHub #708]](https://github.com/ruby/openssl/pull/708) - Make `OpenSSL::SSL::SSLSocket` respect the `IO#timeout` value of the underlying socket on Ruby 3.2 or later. `#timeout` and `#timeout=` methods are also added. [[GitHub #714]](https://github.com/ruby/openssl/pull/714) - Add `OpenSSL::SSL::SSLSocket#close_read` and `#close_write`. [[GitHub #743]](https://github.com/ruby/openssl/pull/743) - Add `OpenSSL::Digest.digests` to get a list of all available digest algorithms. [[GitHub #726]](https://github.com/ruby/openssl/pull/726) - Fix `OpenSSL::SSL::SSLSocket#read_nonblock` clearing the passed String buffer when nothing can be read from the connection. [[GitHub #739]](https://github.com/ruby/openssl/pull/739) * Add `#to_text` methods to `OpenSSL::Timestamp::Response`, `OpenSSL::Timestamp::Request`, `OpenSSL::Timestamp::TokenInfo`, and `OpenSSL::PKCS7` to get a human-readable representation of the object. [[GitHub #756]](https://github.com/ruby/openssl/pull/756) * Add `OpenSSL::X509::Certificate#tbs_bytes` to get the DER encoding of the TBSCertificate. [[GitHub #753]](https://github.com/ruby/openssl/pull/753) * Allow passing `nil` as the digest algorithm to `#sign` methods on `OpenSSL::X509::Certificate`, `OpenSSL::X509::Request`, and `OpenSSL::X509::CRL`. This adds supports for signing with EdDSA keys. [[GitHub #761]](https://github.com/ruby/openssl/pull/761) [[GitHub #804]](https://github.com/ruby/openssl/pull/804) * Add `OpenSSL::SSL::SSLSocket#readbyte`. [[GitHub #771]](https://github.com/ruby/openssl/pull/771) * Change `OpenSSL::X509::Store#time=` to set the time to the `X509_VERIFY_PARAM` in the `X509_STORE`. This allows `OpenSSL::Timestamp::Response#verify` to verify a signature with the specified timestamp. [[GitHub #770]](https://github.com/ruby/openssl/pull/770) * Make `OpenSSL::PKCS7.encrypt`'s third parameter `cipher` mandatory. It had an undocumented default value "RC2-40-CBC", which is not only insecure, but also not supported in OpenSSL 3.0 or later. [[GitHub #796]](https://github.com/ruby/openssl/pull/796) * Make `OpenSSL::BN` shareable between ractors when frozen. [[GitHub #808]](https://github.com/ruby/openssl/pull/808) * Make `OpenSSL::Config` instances frozen by default, and make it shareable between ractors. `OpenSSL::Config::DEFAULT_CONFIG_FILE` is also frozen. [[GitHub #809]](https://github.com/ruby/openssl/pull/809) * Add `OpenSSL::PKCS12#set_mac` to configure the MAC parameters and recalculate a MAC for the content. [[GitHub #788]](https://github.com/ruby/openssl/pull/788) And various non-user-visible changes and bug fixes. Please see the commit history for more details. Version 3.2.3 ============= Merged changes in 3.1.3. Version 3.2.2 ============= Merged changes in 3.1.2. Version 3.2.1 ============= Merged changes in 3.0.3. Version 3.2.0 ============= Compatibility ------------- * Ruby >= 2.7 - Support for Ruby 2.6 has been removed. Note that Ruby 2.6 reached the end-of-life in 2022-04. [[GitHub #639]](https://github.com/ruby/openssl/pull/639) * OpenSSL >= 1.0.2 or LibreSSL >= 3.1 Notable changes --------------- * Add a stub gemspec for JRuby, which depends on the `jruby-openssl` gem. [[GitHub #598]](https://github.com/ruby/openssl/pull/598) * Add support for the FIPS module in OpenSSL 3.0/3.1. [[GitHub #608]](https://github.com/ruby/openssl/pull/608) * Rework `OpenSSL::PKey` routines for loading DER or PEM encoded keys for better compatibility with OpenSSL 3.0/3.1 with the FIPS module. [[GitHub #615]](https://github.com/ruby/openssl/pull/615) [[GitHub #669]](https://github.com/ruby/openssl/pull/669) * Add `OpenSSL::Provider` module for loading and unloading OpenSSL 3 providers. [[GitHub #635]](https://github.com/ruby/openssl/pull/635) * Add `OpenSSL::PKey.new_raw_private_key`, `.new_raw_public_key`, `OpenSSL::PKey::PKey#raw_private_key`, and `#raw_public_key` for public key algorithms that use "raw private/public key", such as X25519 and Ed25519. [[GitHub #646]](https://github.com/ruby/openssl/pull/646) * Improve OpenSSL error messages to include additional information when it is available in OpenSSL's error queue. [[GitHub #648]](https://github.com/ruby/openssl/pull/648) * Change `OpenSSL::SSL::SSLContext#ca_file=` and `#ca_path=` to raise `OpenSSL::SSL::SSLError` instead of printing a warning message. [[GitHub #659]](https://github.com/ruby/openssl/pull/659) * Allow `OpenSSL::X509::ExtensionFactory#create_extension` to take OIDs in the dotted-decimal notation. [[GitHub #141]](https://github.com/ruby/openssl/pull/141) Version 3.1.3 ============= Bug fixes --------- * Fix missing NULL check for `EVP_PKEY_get0()` functions with OpenSSL 3.x. [[GitHub #957]](https://github.com/ruby/openssl/pull/957) Version 3.1.2 ============= Bug fixes --------- * Fix crash when attempting to export an incomplete `OpenSSL::PKey::DSA` key. [[GitHub #845]](https://github.com/ruby/openssl/issues/845) [[GitHub #847]](https://github.com/ruby/openssl/pull/847) * Remove the `OpenSSL::X509::V_FLAG_CRL_CHECK_ALL` flag from the default store used by `OpenSSL::SSL::SSLContext#set_params`. It causes certificate verification to fail with OpenSSL 3.6.0. It has no effect with any other OpenSSL versions. [[GitHub #949]](https://github.com/ruby/openssl/issues/949) [[GitHub #950]](https://github.com/ruby/openssl/pull/950) Version 3.1.1 ============= Merged changes in 3.0.3. Version 3.1.0 ============= Ruby/OpenSSL 3.1 will be maintained for the lifetime of Ruby 3.2. Merged bug fixes in 2.2.3 and 3.0.2. Among the new features and changes are: Notable changes --------------- * Add `OpenSSL::SSL::SSLContext#ciphersuites=` to allow setting TLS 1.3 cipher suites. [[GitHub #493]](https://github.com/ruby/openssl/pull/493) * Add `OpenSSL::SSL::SSLSocket#export_keying_material` for exporting keying material of the session, as defined in RFC 5705. [[GitHub #530]](https://github.com/ruby/openssl/pull/530) * Add `OpenSSL::SSL::SSLContext#keylog_cb=` for setting the TLS key logging callback, which is useful for supporting NSS's SSLKEYLOGFILE debugging output. [[GitHub #536]](https://github.com/ruby/openssl/pull/536) * Remove the default digest algorithm from `OpenSSL::OCSP::BasicResponse#sign` and `OpenSSL::OCSP::Request#sign`. Omitting the 5th parameter of these methods used to be equivalent of specifying SHA-1. This default value is now removed and we will let the underlying OpenSSL library decide instead. [[GitHub #507]](https://github.com/ruby/openssl/pull/507) * Add `OpenSSL::BN#mod_sqrt`. [[GitHub #553]](https://github.com/ruby/openssl/pull/553) * Allow calling `OpenSSL::Cipher#update` with an empty string. This was prohibited to workaround an ancient bug in OpenSSL. [[GitHub #568]](https://github.com/ruby/openssl/pull/568) * Fix build on platforms without socket support, such as WASI. `OpenSSL::SSL` will not be defined if OpenSSL is compiled with `OPENSSL_NO_SOCK`. [[GitHub #558]](https://github.com/ruby/openssl/pull/558) * Improve support for recent LibreSSL versions. This includes HKDF support in LibreSSL 3.6 and Ed25519 support in LibreSSL 3.7. Version 3.0.3 ============= Bug fixes --------- * Fix a performance regression introduced in v2.1.3 on a buffered write to `SSLSocket`. [[GitHub #706]](https://github.com/ruby/openssl/pull/706) * Fix `OpenSSL::PKCS7` to handle PKCS#7 structures without content. [[GitHub #690]](https://github.com/ruby/openssl/pull/690) [[GitHub #752]](https://github.com/ruby/openssl/pull/752) * Fix `OpenSSL::ASN1::ObjectId#==` with OIDs without a known name. [[GitHub #791]](https://github.com/ruby/openssl/issues/791) [[GitHub #792]](https://github.com/ruby/openssl/pull/792) * Fix `OpenSSL::X509::Certificate#crl_uris` to handle CDP with multiple CRL URIs. [[GitHub #775]](https://github.com/ruby/openssl/issues/775) [[GitHub #776]](https://github.com/ruby/openssl/pull/776) * Fix `OpenSSL::Cipher#update` to always make the output buffer `String` independent. [[Bug #20937]](https://bugs.ruby-lang.org/issues/20937) [[GitHub #824]](https://github.com/ruby/openssl/pull/824) Version 3.0.2 ============= Merged changes in 2.2.3. Additionally, the following issues are fixed by this release. Bug fixes --------- * Fix OpenSSL::PKey::EC#check_key not working correctly on OpenSSL 3.0. [[GitHub #563]](https://github.com/ruby/openssl/issues/563) [[GitHub #580]](https://github.com/ruby/openssl/pull/580) Version 3.0.1 ============= Merged changes in 2.1.4 and 2.2.2. Additionally, the following issues are fixed by this release. Bug fixes --------- * Add missing type check in OpenSSL::PKey::PKey#sign's optional parameters. [[GitHub #531]](https://github.com/ruby/openssl/pull/531) * Work around OpenSSL 3.0's HMAC issues with a zero-length key. [[GitHub #538]](https://github.com/ruby/openssl/pull/538) * Fix a regression in OpenSSL::PKey::DSA.generate's default of 'q' size. [[GitHub #483]](https://github.com/ruby/openssl/issues/483) [[GitHub #539]](https://github.com/ruby/openssl/pull/539) * Restore OpenSSL::PKey.read's ability to decode "openssl ecparam -genkey" output when linked against OpenSSL 3.0. [[GitHub #535]](https://github.com/ruby/openssl/pull/535) [[GitHub #540]](https://github.com/ruby/openssl/pull/540) * Restore error checks in OpenSSL::PKey::EC#{to_der,to_pem}. [[GitHub #541]](https://github.com/ruby/openssl/pull/541) Version 3.0.0 ============= Compatibility notes ------------------- * OpenSSL 1.0.1 and Ruby 2.3-2.5 are no longer supported. [[GitHub #396]](https://github.com/ruby/openssl/pull/396) [[GitHub #466]](https://github.com/ruby/openssl/pull/466) * OpenSSL 3.0 support is added. It is the first major version bump from OpenSSL 1.1 and contains incompatible changes that affect Ruby/OpenSSL. Note that OpenSSL 3.0 support is preliminary and not all features are currently available: [[GitHub #369]](https://github.com/ruby/openssl/issues/369) - Deprecate the ability to modify `OpenSSL::PKey::PKey` instances. OpenSSL 3.0 made EVP_PKEY structure immutable, and hence the following methods are not available when Ruby/OpenSSL is linked against OpenSSL 3.0. [[GitHub #480]](https://github.com/ruby/openssl/pull/480) - `OpenSSL::PKey::RSA#set_key`, `#set_factors`, `#set_crt_params` - `OpenSSL::PKey::DSA#set_pqg`, `#set_key` - `OpenSSL::PKey::DH#set_pqg`, `#set_key`, `#generate_key!` - `OpenSSL::PKey::EC#private_key=`, `#public_key=`, `#group=`, `#generate_key!` - Deprecate `OpenSSL::Engine`. The ENGINE API has been deprecated in OpenSSL 3.0 in favor of the new "provider" concept and will be removed in a future version. [[GitHub #481]](https://github.com/ruby/openssl/pull/481) * `OpenSSL::SSL::SSLContext#tmp_ecdh_callback` has been removed. It has been deprecated since v2.0.0 because it is incompatible with modern OpenSSL versions. [[GitHub #394]](https://github.com/ruby/openssl/pull/394) * `OpenSSL::SSL::SSLSocket#read` and `#write` now raise `OpenSSL::SSL::SSLError` if called before a TLS connection is established. Historically, they read/wrote unencrypted data to the underlying socket directly in that case. [[GitHub #9]](https://github.com/ruby/openssl/issues/9) [[GitHub #469]](https://github.com/ruby/openssl/pull/469) Notable changes --------------- * Enhance OpenSSL::PKey's common interface. [[GitHub #370]](https://github.com/ruby/openssl/issues/370) - Key deserialization: Enhance `OpenSSL::PKey.read` to handle PEM encoding of DH parameters, which used to be only deserialized by `OpenSSL::PKey::DH.new`. [[GitHub #328]](https://github.com/ruby/openssl/issues/328) - Key generation: Add `OpenSSL::PKey.generate_parameters` and `OpenSSL::PKey.generate_key`. [[GitHub #329]](https://github.com/ruby/openssl/issues/329) - Public key signing: Enhance `OpenSSL::PKey::PKey#sign` and `#verify` to use the new EVP_DigestSign() family to enable PureEdDSA support on OpenSSL 1.1.1 or later. They also now take optional algorithm-specific parameters for more control. [[GitHub #329]](https://github.com/ruby/openssl/issues/329) - Low-level public key signing and verification: Add `OpenSSL::PKey::PKey#sign_raw`, `#verify_raw`, and `#verify_recover`. [[GitHub #382]](https://github.com/ruby/openssl/issues/382) - Public key encryption: Add `OpenSSL::PKey::PKey#encrypt` and `#decrypt`. [[GitHub #382]](https://github.com/ruby/openssl/issues/382) - Key agreement: Add `OpenSSL::PKey::PKey#derive`. [[GitHub #329]](https://github.com/ruby/openssl/issues/329) - Key comparison: Add `OpenSSL::PKey::PKey#compare?` to conveniently check that two keys have common parameters and a public key. [[GitHub #383]](https://github.com/ruby/openssl/issues/383) * Add `OpenSSL::BN#set_flags` and `#get_flags`. This can be used in combination with `OpenSSL::BN::CONSTTIME` to force constant-time computation. [[GitHub #417]](https://github.com/ruby/openssl/issues/417) * Add `OpenSSL::BN#abs` to get the absolute value of the BIGNUM. [[GitHub #430]](https://github.com/ruby/openssl/issues/430) * Add `OpenSSL::SSL::SSLSocket#getbyte`. [[GitHub #438]](https://github.com/ruby/openssl/issues/438) * Add `OpenSSL::SSL::SSLContext#tmp_dh=`. [[GitHub #459]](https://github.com/ruby/openssl/pull/459) * Add `OpenSSL::X509::Certificate.load` to load a PEM-encoded and concatenated list of X.509 certificates at once. [[GitHub #441]](https://github.com/ruby/openssl/pull/441) * Change `OpenSSL::X509::Certificate.new` to attempt to deserialize the given string first as DER encoding first and then as PEM encoding to ensure the round-trip consistency. [[GitHub #442]](https://github.com/ruby/openssl/pull/442) * Update various part of the code base to use the modern API. No breaking changes are intended with this. This includes: - `OpenSSL::HMAC` uses the EVP API. [[GitHub #371]](https://github.com/ruby/openssl/issues/371) - `OpenSSL::Config` uses native OpenSSL API to parse config files. [[GitHub #342]](https://github.com/ruby/openssl/issues/342) Version 2.2.3 ============= Bug fixes --------- * Fix serveral methods in OpenSSL::PKey::EC::Point attempting to raise an error with an incorrect class, which would end up with a TypeError. [[GitHub #570]](https://github.com/ruby/openssl/pull/570) * Fix OpenSSL::PKey::EC::Point#eql? and OpenSSL::PKey::EC::Group#eql? incorrectly treated OpenSSL's internal errors as "not equal". [[GitHub #564]](https://github.com/ruby/openssl/pull/564) * Fix build with LibreSSL 3.5 or later. Version 2.2.2 ============= Merged changes in 2.1.4. Version 2.2.1 ============= Merged changes in 2.1.3. Additionally, the following issues are fixed by this release. Bug fixes --------- * Fix crash in `OpenSSL::Timestamp::{Request,Response,TokenInfo}.new` when invalid arguments are given. [[GitHub #407]](https://github.com/ruby/openssl/pull/407) * Fix `OpenSSL::Timestamp::Factory#create_timestamp` with LibreSSL on platforms where `time_t` has a different size from `long`. [[GitHub #454]](https://github.com/ruby/openssl/pull/454) Version 2.2.0 ============= Compatibility notes ------------------- * Remove unsupported MDC2, DSS, DSS1, and SHA algorithms. * Remove `OpenSSL::PKCS7::SignerInfo#name` alias for `#issuer`. [[GitHub #266]](https://github.com/ruby/openssl/pull/266) * Deprecate `OpenSSL::Config#add_value` and `#[]=` for future removal. [[GitHub #322]](https://github.com/ruby/openssl/pull/322) Notable changes --------------- * Change default `OpenSSL::SSL::SSLServer#listen` backlog argument from 5 to `Socket::SOMAXCONN`. [[GitHub #286]](https://github.com/ruby/openssl/issues/286) * Make `OpenSSL::HMAC#==` use a timing safe string comparison. [[GitHub #284]](https://github.com/ruby/openssl/pull/284) * Add support for SHA3 and BLAKE digests. [[GitHub #282]](https://github.com/ruby/openssl/pull/282) * Add `OpenSSL::SSL::SSLSocket.open` for opening a `TCPSocket` and returning an `OpenSSL::SSL::SSLSocket` for it. [[GitHub #225]](https://github.com/ruby/openssl/issues/225) * Support marshalling of `OpenSSL::X509` and `OpenSSL::PKey` objects. [[GitHub #281]](https://github.com/ruby/openssl/pull/281) [[GitHub #363]](https://github.com/ruby/openssl/pull/363) * Add `OpenSSL.secure_compare` for timing safe string comparison for strings of possibly unequal length. [[GitHub #280]](https://github.com/ruby/openssl/pull/280) * Add `OpenSSL.fixed_length_secure_compare` for timing safe string comparison for strings of equal length. [[GitHub #269]](https://github.com/ruby/openssl/pull/269) * Add `OpenSSL::SSL::SSLSocket#{finished_message,peer_finished_message}` for last finished message sent and received. [[GitHub #250]](https://github.com/ruby/openssl/pull/250) * Add `OpenSSL::Timestamp` module for handing timestamp requests and responses. [[GitHub #204]](https://github.com/ruby/openssl/pull/204) * Add helper methods for `OpenSSL::X509::Certificate`: `find_extension`, `subject_key_identifier`, `authority_key_identifier`, `crl_uris`, `ca_issuer_uris` and `ocsp_uris`, and for `OpenSSL::X509::CRL`: `find_extension` and `subject_key_identifier`. [[GitHub #260]](https://github.com/ruby/openssl/pull/260) [[GitHub #275]](https://github.com/ruby/openssl/pull/275) [[GitHub #293]](https://github.com/ruby/openssl/pull/293) * Add `OpenSSL::ECPoint#add` for performing elliptic curve point addition. [[GitHub #261]](https://github.com/ruby/openssl/pull/261) * Make `OpenSSL::PKey::RSA#{export,to_der}` check `key`, `factors`, and `crt_params` to do proper private key serialization. [[GitHub #258]](https://github.com/ruby/openssl/pull/258) * Add `OpenSSL::SSL::{SSLSocket,SSLServer}#fileno`, returning the underlying socket file descriptor number. [[GitHub #247]](https://github.com/ruby/openssl/pull/247) * Support client certificates with TLS 1.3, and support post-handshake authentication with OpenSSL 1.1.1+. [[GitHub #239]](https://github.com/ruby/openssl/pull/239) * Add `OpenSSL::ASN1::ObjectId#==` for equality testing. * Add `OpenSSL::X509::Extension#value_der` for the raw value of the extension. [[GitHub #234]](https://github.com/ruby/openssl/pull/234) * Significantly reduce allocated memory in `OpenSSL::Buffering#do_write`. [[GitHub #212]](https://github.com/ruby/openssl/pull/212) * Ensure all valid IPv6 addresses are considered valid as elements of subjectAlternativeName in certificates. [[GitHub #185]](https://github.com/ruby/openssl/pull/185) * Allow recipient's certificate to be omitted in PCKS7#decrypt. [[GitHub #183]](https://github.com/ruby/openssl/pull/183) * Add support for reading keys in PKCS #8 format and export via instance methods added to `OpenSSL::PKey` classes: `private_to_der`, `private_to_pem`, `public_to_der` and `public_to_pem`. [[GitHub #297]](https://github.com/ruby/openssl/pull/297) Version 2.1.4 ============= Bug fixes --------- * Do not use pkg-config if --with-openssl-dir option is specified. [[GitHub #486]](https://github.com/ruby/openssl/pull/486) Version 2.1.3 ============= Bug fixes --------- * Fix deprecation warnings on Ruby 3.0. * Add ".include" directive support in `OpenSSL::Config`. [[GitHub #216]](https://github.com/ruby/openssl/pull/216) * Fix handling of IPv6 address SANs. [[GitHub #185]](https://github.com/ruby/openssl/pull/185) * Hostname verification failure with `OpenSSL::SSL::SSLContext#verify_hostname=` sets a proper error code. [[GitHub #350]](https://github.com/ruby/openssl/pull/350) * Fix crash with `OpenSSL::BN.new(nil, 2)`. [[Bug #15760]](https://bugs.ruby-lang.org/issues/15760) * `OpenSSL::SSL::SSLSocket#sys{read,write}` prevent internal string buffers from being modified by another thread. [[GitHub #453]](https://github.com/ruby/openssl/pull/453) * Fix misuse of input record separator in `OpenSSL::Buffering` where it was for output. * Fix wrong integer casting in `OpenSSL::PKey::EC#dsa_verify_asn1`. [[GitHub #460]](https://github.com/ruby/openssl/pull/460) * `extconf.rb` explicitly checks that OpenSSL's version number is 1.0.1 or newer but also less than 3.0. Ruby/OpenSSL v2.1.x and v2.2.x will not support OpenSSL 3.0 API. [[GitHub #458]](https://github.com/ruby/openssl/pull/458) * Activate `digest` gem correctly. `digest` library could go into an inconsistent state if there are multiple versions of `digest` is installed and `openssl` is `require`d before `digest`. [[GitHub #463]](https://github.com/ruby/openssl/pull/463) * Fix GC.compact compatibility. [[GitHub #464]](https://github.com/ruby/openssl/issues/464) [[GitHub #465]](https://github.com/ruby/openssl/pull/465) Version 2.1.2 ============= Merged changes in 2.0.9. Version 2.1.1 ============= Merged changes in 2.0.8. Version 2.1.0 ============= Notable changes --------------- * Support for OpenSSL versions before 1.0.1 and LibreSSL versions before 2.5 is removed. [[GitHub #86]](https://github.com/ruby/openssl/pull/86) * OpenSSL::BN#negative?, #+@, and #-@ are added. * OpenSSL::SSL::SSLSocket#connect raises a more informative exception when certificate verification fails. [[GitHub #99]](https://github.com/ruby/openssl/pull/99) * OpenSSL::KDF module is newly added. In addition to PBKDF2-HMAC that has moved from OpenSSL::PKCS5, scrypt and HKDF are supported. [[GitHub #109]](https://github.com/ruby/openssl/pull/109) [[GitHub #173]](https://github.com/ruby/openssl/pull/173) * OpenSSL.fips_mode is added. We had the setter, but not the getter. [[GitHub #125]](https://github.com/ruby/openssl/pull/125) * OpenSSL::OCSP::Request#signed? is added. * OpenSSL::ASN1 handles the indefinite length form better. OpenSSL::ASN1.decode no longer wrongly treats the end-of-contents octets as part of the content. OpenSSL::ASN1::ASN1Data#infinite_length is renamed to #indefinite_length. [[GitHub #98]](https://github.com/ruby/openssl/pull/98) * OpenSSL::X509::Name#add_entry now accepts two additional keyword arguments 'loc' and 'set'. [[GitHub #94]](https://github.com/ruby/openssl/issues/94) * OpenSSL::SSL::SSLContext#min_version= and #max_version= are added to replace #ssl_version= that was built on top of the deprecated OpenSSL C API. Use of that method and the constant OpenSSL::SSL::SSLContext::METHODS is now deprecated. [[GitHub #142]](https://github.com/ruby/openssl/pull/142) * OpenSSL::X509::Name#to_utf8 is added. [[GitHub #26]](https://github.com/ruby/openssl/issues/26) [[GitHub #143]](https://github.com/ruby/openssl/pull/143) * OpenSSL::X509::{Extension,Attribute,Certificate,CRL,Revoked,Request} can be compared with == operator. [[GitHub #161]](https://github.com/ruby/openssl/pull/161) * TLS Fallback Signaling Cipher Suite Value (SCSV) support is added. [[GitHub #165]](https://github.com/ruby/openssl/pull/165) * Build failure with OpenSSL 1.1 built with no-deprecated is fixed. [[GitHub #160]](https://github.com/ruby/openssl/pull/160) * OpenSSL::Buffering#write accepts an arbitrary number of arguments. [[Feature #9323]](https://bugs.ruby-lang.org/issues/9323) [[GitHub #162]](https://github.com/ruby/openssl/pull/162) * OpenSSL::PKey::RSA#sign_pss and #verify_pss are added. They perform RSA-PSS signature and verification. [[GitHub #75]](https://github.com/ruby/openssl/issues/75) [[GitHub #76]](https://github.com/ruby/openssl/pull/76) [[GitHub #169]](https://github.com/ruby/openssl/pull/169) * OpenSSL::SSL::SSLContext#add_certificate is added. [[GitHub #167]](https://github.com/ruby/openssl/pull/167) * OpenSSL::PKey::EC::Point#to_octet_string is added. OpenSSL::PKey::EC::Point.new can now take String as the second argument. [[GitHub #177]](https://github.com/ruby/openssl/pull/177) Version 2.0.9 ============= Security fixes -------------- * OpenSSL::X509::Name#<=> could incorrectly return 0 (= equal) for non-equal objects. CVE-2018-16395 is assigned for this issue. https://hackerone.com/reports/387250 Bug fixes --------- * Fixed OpenSSL::PKey::\*.{new,generate} immediately aborting if the thread is interrupted. [[Bug #14882]](https://bugs.ruby-lang.org/issues/14882) [[GitHub #205]](https://github.com/ruby/openssl/pull/205) * Fixed OpenSSL::X509::Name#to_s failing with OpenSSL::X509::NameError if called against an empty instance. [[GitHub #200]](https://github.com/ruby/openssl/issues/200) [[GitHub #211]](https://github.com/ruby/openssl/pull/211) Version 2.0.8 ============= Bug fixes --------- * OpenSSL::Cipher#pkcs5_keyivgen raises an error when a negative iteration count is given. [[GitHub #184]](https://github.com/ruby/openssl/pull/184) * Fixed build with LibreSSL 2.7. [[GitHub #192]](https://github.com/ruby/openssl/issues/192) [[GitHub #193]](https://github.com/ruby/openssl/pull/193) Version 2.0.7 ============= Bug fixes --------- * OpenSSL::Cipher#auth_data= could segfault if called against a non-AEAD cipher. [[Bug #14024]](https://bugs.ruby-lang.org/issues/14024) * OpenSSL::X509::Certificate#public_key= (and similar methods) could segfault when an instance of OpenSSL::PKey::PKey with no public key components is passed. [[Bug #14087]](https://bugs.ruby-lang.org/issues/14087) [[GitHub #168]](https://github.com/ruby/openssl/pull/168) Version 2.0.6 ============= Bug fixes --------- * The session_remove_cb set to an OpenSSL::SSL::SSLContext is no longer called during GC. * A possible deadlock in OpenSSL::SSL::SSLSocket#sysread is fixed. [[GitHub #139]](https://github.com/ruby/openssl/pull/139) * OpenSSL::BN#hash could return an unnormalized fixnum value on Windows. [[Bug #13877]](https://bugs.ruby-lang.org/issues/13877) * OpenSSL::SSL::SSLSocket#sysread and #sysread_nonblock set the length of the destination buffer String to 0 on error. [[GitHub #153]](https://github.com/ruby/openssl/pull/153) * Possible deadlock is fixed. This happened only when built with older versions of OpenSSL (before 1.1.0) or LibreSSL. [[GitHub #155]](https://github.com/ruby/openssl/pull/155) Version 2.0.5 ============= Bug fixes --------- * Reading a PEM/DER-encoded private key or certificate from an IO object did not work properly on mswin platforms. [[ruby/openssl#128]](https://github.com/ruby/openssl/issues/128) * Broken length check in the PEM passphrase callback is fixed. * It failed to compile when OpenSSL is configured without TLS 1.0 support. 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::SSLContext#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. ================================================ FILE: README.md ================================================ # OpenSSL for Ruby [![Actions Status](https://github.com/ruby/openssl/workflows/CI/badge.svg)](https://github.com/ruby/openssl/actions?workflow=CI) **OpenSSL for Ruby** provides access to SSL/TLS and general-purpose cryptography based on the OpenSSL library. OpenSSL for Ruby is sometimes referred to as **openssl** in all lowercase or **Ruby/OpenSSL** for disambiguation. ## Compatibility and maintenance policy OpenSSL for Ruby is released as a RubyGems gem. At the same time, it is part of the standard library of Ruby. This is called a [default gem]. Each stable branch of OpenSSL for Ruby will remain supported as long as it is included as a default gem in [supported Ruby branches][Ruby Maintenance Branches]. |Version|Minimum Ruby|OpenSSL compatibility |Bundled with|Maintenance | |-------|------------|-----------------------------------------|------------|-------------| |4.0.x |Ruby 2.7 |OpenSSL 1.1.1-3.x, LibreSSL 3.9+, AWS-LC |Ruby 4.0 |bug fixes | |3.3.x |Ruby 2.7 |OpenSSL 1.0.2-3.x, LibreSSL 3.1+ |Ruby 3.4 |bug fixes | |3.2.x |Ruby 2.7 |OpenSSL 1.0.2-3.x, LibreSSL 3.1+ |Ruby 3.3 |bug fixes | |3.1.x |Ruby 2.6 |OpenSSL 1.0.2-3.x, LibreSSL 3.1+ |Ruby 3.2 |security only| |3.0.x |Ruby 2.6 |OpenSSL 1.0.2-3.x, LibreSSL 3.1+ |Ruby 3.1 |end-of-life | |2.2.x |Ruby 2.3 |OpenSSL 1.0.1-1.1.1, LibreSSL 2.9+ |Ruby 3.0 |end-of-life | |2.1.x |Ruby 2.3 |OpenSSL 1.0.1-1.1.1, LibreSSL 2.5+ |Ruby 2.5-2.7|end-of-life | |2.0.x |Ruby 2.3 |OpenSSL 0.9.8-1.1.1, LibreSSL 2.3+ |Ruby 2.4 |end-of-life | [default gem]: https://docs.ruby-lang.org/en/master/standard_library_md.html [Ruby Maintenance Branches]: https://www.ruby-lang.org/en/downloads/branches/ ## Installation > **Note** > The openssl gem is included with Ruby by default, but you may wish to upgrade > it to a newer version available at [rubygems.org][RubyGems.org openssl]. To upgrade it, you can use RubyGems: ``` gem install openssl ``` In some cases, it may be necessary to specify the path to the installation directory of the OpenSSL library. ``` 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', git: 'https://github.com/ruby/openssl' ``` After running `bundle install`, you should have the gem installed in your bundle. [RubyGems.org openssl]: https://rubygems.org/gems/openssl ## Usage Once installed, you can require "openssl" in your application. ```ruby require "openssl" ``` ## Documentation See https://ruby.github.io/openssl/. ## Contributing Please read our [CONTRIBUTING.md] for instructions. [CONTRIBUTING.md]: https://github.com/ruby/openssl/tree/master/CONTRIBUTING.md ## Security Security issues should be reported to ruby-core by following the process described on ["Security at ruby-lang.org"][Security]. [Security]: https://www.ruby-lang.org/en/security/ ================================================ FILE: Rakefile ================================================ require 'rake/testtask' require 'rdoc/task' require 'bundler/gem_tasks' begin require 'rake/extensiontask' Rake::ExtensionTask.new('openssl') rescue LoadError warn "rake-compiler not installed. Run 'bundle install' to " \ "install testing dependency gems." end task :test => :compile Rake::TestTask.new do |t| t.test_files = FileList["test/**/test_*.rb"] t.warning = true end desc 'Run tests for fips' task :test_fips => :compile do ENV['TEST_RUBY_OPENSSL_FIPS_ENABLED'] = 'true' Rake::Task['test_fips_internal'].invoke end Rake::TestTask.new(:test_fips_internal) do |t| # Exclude failing test files in FIPS for this task to pass. # TODO: Fix failing test files. t.test_files = FileList['test/**/test_*.rb'] - FileList[ 'test/openssl/test_hmac.rb', 'test/openssl/test_kdf.rb', 'test/openssl/test_ts.rb', ] t.warning = true end RDoc::Task.new do |rdoc| rdoc.main = "README.md" rdoc.rdoc_files.include("*.md", "lib/**/*.rb", "ext/**/*.c") end # Print Ruby and compiler info for debugging purpose. task :debug_compiler do compiler = RbConfig::CONFIG['CC'] case compiler when 'gcc', 'clang' sh "#{compiler} --version" else Rake.rake_output_message "Compiler: #{RbConfig::CONFIG['CC']}" end end task :debug do ruby_code = <<~'EOF' openssl_version_number_str = OpenSSL::OPENSSL_VERSION_NUMBER.to_s(16) libressl_version_number_str = (defined? OpenSSL::LIBRESSL_VERSION_NUMBER) ? OpenSSL::LIBRESSL_VERSION_NUMBER.to_s(16) : "undefined" providers_str = (defined? OpenSSL::Provider) ? OpenSSL::Provider.provider_names.join(", ") : "undefined" puts <<~MESSAGE OpenSSL::OPENSSL_VERSION: #{OpenSSL::OPENSSL_VERSION} OpenSSL::OPENSSL_LIBRARY_VERSION: #{OpenSSL::OPENSSL_LIBRARY_VERSION} OpenSSL::OPENSSL_VERSION_NUMBER: #{openssl_version_number_str} OpenSSL::LIBRESSL_VERSION_NUMBER: #{libressl_version_number_str} FIPS enabled: #{OpenSSL.fips_mode} Providers: #{providers_str} MESSAGE EOF ruby %Q(-I./lib -ropenssl.so -e'#{ruby_code}'), verbose: false end task :default => :test ================================================ FILE: ext/openssl/extconf.rb ================================================ # -*- coding: us-ascii -*- # frozen_string_literal: true =begin = Info 'OpenSSL for Ruby 2' project Copyright (C) 2002 Michal Rokos All rights reserved. = Licence This program is licensed under the same licence as Ruby. (See the file 'COPYING'.) =end require "mkmf" ssl_dirs = dir_config("openssl") dir_config_given = ssl_dirs.any? _, ssl_ldir = ssl_dirs if ssl_ldir&.split(File::PATH_SEPARATOR)&.none? { |dir| File.directory?(dir) } # According to the `mkmf.rb#dir_config`, the `--with-openssl-dir=` uses # the value of the `File.basename(RbConfig::MAKEFILE_CONFIG["libdir"])` as a # loaded library directory name. ruby_ldir_name = File.basename(RbConfig::MAKEFILE_CONFIG["libdir"]) raise "OpenSSL library directory could not be found in '#{ssl_ldir}'. " \ "You might want to fix this error in one of the following ways.\n" \ " * Recompile OpenSSL by configuring it with --libdir=#{ruby_ldir_name} " \ " to specify the OpenSSL library directory.\n" \ " * Recompile Ruby by configuring it with --libdir= to specify the " \ "Ruby library directory.\n" \ " * Compile this openssl gem with --with-openssl-include= and " \ "--with-openssl-lib= options to specify the OpenSSL include and " \ "library directories." end Logging::message "=== OpenSSL for Ruby configurator ===\n" $defs.push("-D""OPENSSL_SUPPRESS_DEPRECATED") # Missing in TruffleRuby have_func("rb_call_super_kw(0, NULL, 0)", "ruby.h") # Ruby 3.1 have_func("rb_io_descriptor", "ruby/io.h") have_func("rb_io_maybe_wait(0, Qnil, Qnil, Qnil)", "ruby/io.h") # Ruby 3.2 have_func("rb_io_timeout", "ruby/io.h") Logging::message "=== Checking for system dependent stuff... ===\n" have_library("nsl", "t_open") have_library("socket", "socket") if $mswin || $mingw have_library("ws2_32") end if $mingw append_cflags '-D_FORTIFY_SOURCE=2' append_ldflags '-fstack-protector' have_library 'ssp' end def find_openssl_library if $mswin || $mingw # required for static OpenSSL libraries 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 # 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 Logging::message "=== Checking for required stuff... ===\n" pkg_config_found = !dir_config_given && pkg_config("openssl") && have_header("openssl/ssl.h") if !pkg_config_found && !find_openssl_library Logging::message "=== Checking for required stuff failed. ===\n" Logging::message "Makefile wasn't created. Fix the errors above.\n" raise "OpenSSL library could not be found. You might want to use " \ "--with-openssl-dir= option to specify the prefix where OpenSSL " \ "is installed." end version_ok = if have_macro("LIBRESSL_VERSION_NUMBER", "openssl/opensslv.h") is_libressl = true checking_for("LibreSSL version >= 3.9.0") { try_static_assert("LIBRESSL_VERSION_NUMBER >= 0x30900000L", "openssl/opensslv.h") } else is_openssl = true checking_for("OpenSSL version >= 1.1.1") { try_static_assert("OPENSSL_VERSION_NUMBER >= 0x10101000L", "openssl/opensslv.h") } end unless version_ok raise "OpenSSL >= 1.1.1 or LibreSSL >= 3.9.0 is required" end # Prevent wincrypt.h from being included, which defines conflicting macro with openssl/x509.h if is_libressl && ($mswin || $mingw) $defs.push("-DNOCRYPT") end Logging::message "=== Checking for OpenSSL features... ===\n" evp_h = "openssl/evp.h".freeze ts_h = "openssl/ts.h".freeze ssl_h = "openssl/ssl.h".freeze # compile options have_func("RAND_egd()", "openssl/rand.h") # added in OpenSSL 1.0.2, not in LibreSSL yet have_func("SSL_CTX_set1_sigalgs_list(NULL, NULL)", ssl_h) # added in OpenSSL 1.0.2, not in LibreSSL or AWS-LC yet have_func("SSL_CTX_set1_client_sigalgs_list(NULL, NULL)", ssl_h) # added in 1.1.0, currently not in LibreSSL have_func("EVP_PBE_scrypt(\"\", 0, (unsigned char *)\"\", 0, 0, 0, 0, 0, NULL, 0)", evp_h) # added in OpenSSL 1.1.1 and LibreSSL 3.5.0, then removed in LibreSSL 4.0.0 have_func("EVP_PKEY_check(NULL)", evp_h) # added in 3.0.0 have_func("SSL_CTX_set0_tmp_dh_pkey(NULL, NULL)", ssl_h) have_func("ERR_get_error_all(NULL, NULL, NULL, NULL, NULL)", "openssl/err.h") have_func("SSL_CTX_load_verify_file(NULL, \"\")", ssl_h) have_func("BN_check_prime(NULL, NULL, NULL)", "openssl/bn.h") have_func("EVP_MD_CTX_get0_md(NULL)", evp_h) have_func("EVP_MD_CTX_get_pkey_ctx(NULL)", evp_h) have_func("EVP_PKEY_eq(NULL, NULL)", evp_h) have_func("EVP_PKEY_dup(NULL)", evp_h) # added in 3.2.0 have_func("SSL_get0_group_name(NULL)", ssl_h) # added in 3.4.0 have_func("TS_VERIFY_CTX_set0_certs(NULL, NULL)", ts_h) # added in 3.5.0 have_func("SSL_get0_peer_signature_name(NULL, NULL)", ssl_h) # added in 4.0.0 have_func("ASN1_BIT_STRING_set1(NULL, NULL, 0, 0)", "openssl/asn1.h") Logging::message "=== Checking done. ===\n" # Append flags from environment variables. extcflags = ENV["RUBY_OPENSSL_EXTCFLAGS"] append_cflags(extcflags.split) if extcflags extldflags = ENV["RUBY_OPENSSL_EXTLDFLAGS"] append_ldflags(extldflags.split) if extldflags create_header create_makefile("openssl") Logging::message "Done.\n" ================================================ FILE: ext/openssl/openssl_missing.h ================================================ /* * 'OpenSSL for Ruby' project * Copyright (C) 2001-2002 Michal Rokos * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #if !defined(_OSSL_OPENSSL_MISSING_H_) #define _OSSL_OPENSSL_MISSING_H_ #include "ruby/config.h" /* added in 3.0.0 */ #ifndef HAVE_EVP_MD_CTX_GET0_MD # define EVP_MD_CTX_get0_md(ctx) EVP_MD_CTX_md(ctx) #endif /* * OpenSSL 1.1.0 added EVP_MD_CTX_pkey_ctx(), and then it was renamed to * EVP_MD_CTX_get_pkey_ctx(x) in OpenSSL 3.0. */ #ifndef HAVE_EVP_MD_CTX_GET_PKEY_CTX # define EVP_MD_CTX_get_pkey_ctx(x) EVP_MD_CTX_pkey_ctx(x) #endif #ifndef HAVE_EVP_PKEY_EQ # define EVP_PKEY_eq(a, b) EVP_PKEY_cmp(a, b) #endif /* added in 4.0.0 */ #ifndef HAVE_ASN1_BIT_STRING_SET1 static inline int ASN1_BIT_STRING_set1(ASN1_BIT_STRING *bitstr, const uint8_t *data, size_t length, int unused_bits) { if (length > INT_MAX || !ASN1_STRING_set(bitstr, data, (int)length)) return 0; bitstr->flags &= ~(ASN1_STRING_FLAG_BITS_LEFT | 0x07); bitstr->flags |= ASN1_STRING_FLAG_BITS_LEFT | unused_bits; return 1; } static inline int ASN1_BIT_STRING_get_length(const ASN1_BIT_STRING *bitstr, size_t *length, int *unused_bits) { *length = bitstr->length; *unused_bits = bitstr->flags & 0x07; return 1; } #endif #endif /* _OSSL_OPENSSL_MISSING_H_ */ ================================================ FILE: ext/openssl/ossl.c ================================================ /* * 'OpenSSL for Ruby' project * Copyright (C) 2001-2002 Michal Rokos * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #include "ossl.h" #include /* for ossl_raise */ /* * Data Conversion */ #define OSSL_IMPL_ARY2SK(name, type, expected_class, dup) \ VALUE \ 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 */ \ if (!sk_##type##_push(sk, x)) { \ type##_free(x); \ sk_##type##_pop_free(sk, type##_free); \ ossl_raise(eOSSLError, NULL); \ } \ } \ return (VALUE)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; \ \ RUBY_ASSERT(sk != NULL); \ num = sk_##type##_num(sk); \ ary = rb_ary_new_capa(num); \ \ for (i=0; i> 4]; out[i * 2 + 1] = hex[p & 0x0f]; } } /* * our default PEM callback */ VALUE ossl_pem_passwd_value(VALUE pass) { if (NIL_P(pass)) return Qnil; StringValue(pass); /* 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 = rb_yield(flag); if (NIL_P(pass)) return Qnil; StringValue(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 <= 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 password * 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; } if (NIL_P(pass)) return -1; len = RSTRING_LEN(pass); 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 */ static 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 ID id_i_errors; static void collect_errors_into(VALUE ary); VALUE ossl_make_error(VALUE exc, VALUE str) { unsigned long e; const char *data; int flags; VALUE errors = rb_ary_new(); if (NIL_P(str)) str = rb_str_new(NULL, 0); #ifdef HAVE_ERR_GET_ERROR_ALL e = ERR_peek_last_error_all(NULL, NULL, NULL, &data, &flags); #else e = ERR_peek_last_error_line_data(NULL, NULL, &data, &flags); #endif if (e) { const char *msg = ERR_reason_error_string(e); if (RSTRING_LEN(str)) rb_str_cat_cstr(str, ": "); rb_str_cat_cstr(str, msg ? msg : "(null)"); if (flags & ERR_TXT_STRING && data) rb_str_catf(str, " (%s)", data); collect_errors_into(errors); } VALUE obj = rb_exc_new_str(exc, str); rb_ivar_set(obj, id_i_errors, errors); return obj; } void ossl_raise(VALUE exc, const char *fmt, ...) { va_list args; VALUE err; if (fmt) { va_start(args, fmt); err = rb_vsprintf(fmt, args); va_end(args); } else { err = Qnil; } rb_exc_raise(ossl_make_error(exc, err)); } static void collect_errors_into(VALUE ary) { if (dOSSL == Qtrue || !NIL_P(ary)) { unsigned long e; const char *file, *data, *func, *lib, *reason; int line, flags; #ifdef HAVE_ERR_GET_ERROR_ALL while ((e = ERR_get_error_all(&file, &line, &func, &data, &flags))) { #else while ((e = ERR_get_error_line_data(&file, &line, &data, &flags))) { func = ERR_func_error_string(e); #endif lib = ERR_lib_error_string(e); reason = ERR_reason_error_string(e); VALUE str = rb_sprintf("error:%08lX:%s:%s:%s", e, lib ? lib : "", func ? func : "", reason ? reason : ""); if (flags & ERR_TXT_STRING) { if (!data) data = "(null)"; rb_str_catf(str, " (%s)", data); } if (dOSSL == Qtrue) rb_warn("error on stack: %"PRIsVALUE, str); if (!NIL_P(ary)) rb_ary_push(ary, str); } } else { ERR_clear_error(); } } void ossl_clear_error(void) { collect_errors_into(Qnil); } /* * call-seq: * ossl_error.detailed_message(**) -> string * * Returns the exception message decorated with the captured \OpenSSL error * queue entries. */ static VALUE osslerror_detailed_message(int argc, VALUE *argv, VALUE self) { VALUE str; #ifdef HAVE_RB_CALL_SUPER_KW // Ruby >= 3.2 if (RTEST(rb_funcall(rb_eException, rb_intern("method_defined?"), 1, ID2SYM(rb_intern("detailed_message"))))) str = rb_call_super_kw(argc, argv, RB_PASS_CALLED_KEYWORDS); else #endif str = rb_funcall(self, rb_intern("message"), 0); VALUE errors = rb_attr_get(self, id_i_errors); // OpenSSLError was not created by ossl_make_error() if (!RB_TYPE_P(errors, T_ARRAY)) return str; str = rb_str_resurrect(str); rb_str_catf(str, "\nOpenSSL error queue reported %ld errors:", RARRAY_LEN(errors)); for (long i = 0; i < RARRAY_LEN(errors); i++) { VALUE err = RARRAY_AREF(errors, i); rb_str_catf(str, "\n%"PRIsVALUE, err); } return str; } /* * call-seq: * OpenSSL.errors -> [String...] * * Returns any remaining errors held in the \OpenSSL thread-local error queue * and clears the queue. This should normally return an empty array. * * This is intended for debugging Ruby/OpenSSL. If you see any errors here, * it likely indicates a bug in the extension. Please file an issue at * https://github.com/ruby/openssl. * * For debugging your program, OpenSSL.debug= may be useful. */ static VALUE ossl_get_errors(VALUE _) { 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; /* * call-seq: * OpenSSL.debug -> true | false * * Returns whether Ruby/OpenSSL's debug mode is currently enabled. */ static VALUE ossl_debug_get(VALUE self) { return dOSSL; } /* * call-seq: * OpenSSL.debug = boolean * * Turns on or off debug mode. With debug mode, all errors 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 -> true | false * * Returns whether the FIPS mode is currently enabled. */ static VALUE ossl_fips_mode_get(VALUE self) { #if OSSL_OPENSSL_PREREQ(3, 0, 0) VALUE enabled; enabled = EVP_default_properties_is_fips_enabled(NULL) ? Qtrue : Qfalse; return enabled; #elif defined(OPENSSL_FIPS) || defined(OPENSSL_IS_AWSLC) VALUE enabled; enabled = FIPS_mode() ? Qtrue : Qfalse; return enabled; #else return Qfalse; #endif } /* * call-seq: * OpenSSL.fips_mode = 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) { #if OSSL_OPENSSL_PREREQ(3, 0, 0) if (RTEST(enabled)) { if (!EVP_default_properties_enable_fips(NULL, 1)) { ossl_raise(eOSSLError, "Turning on FIPS mode failed"); } } else { if (!EVP_default_properties_enable_fips(NULL, 0)) { ossl_raise(eOSSLError, "Turning off FIPS mode failed"); } } return enabled; #elif defined(OPENSSL_FIPS) || defined(OPENSSL_IS_AWSLC) 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 } /* * call-seq: * OpenSSL.fixed_length_secure_compare(string, string) -> true or false * * Constant time memory comparison for fixed length strings, such as results * of \HMAC calculations. * * Returns +true+ if the strings are identical, +false+ if they are of the same * length but not identical. If the length is different, ArgumentError is * raised. */ static VALUE ossl_crypto_fixed_length_secure_compare(VALUE dummy, VALUE str1, VALUE str2) { const unsigned char *p1; const unsigned char *p2; long len1; long len2; StringValue(str1); StringValue(str2); p1 = (const unsigned char *)RSTRING_PTR(str1); p2 = (const unsigned char *)RSTRING_PTR(str2); len1 = RSTRING_LEN(str1); len2 = RSTRING_LEN(str2); if (len1 != len2) { ossl_raise(rb_eArgError, "inputs must be of equal length"); } switch (CRYPTO_memcmp(p1, p2, len1)) { case 0: return Qtrue; default: return Qfalse; } } /* * OpenSSL provides \SSL, TLS and general purpose cryptography. It wraps the * OpenSSL[https://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 * * File.write 'private_key.pem', key.private_to_pem * File.write 'public_key.pem', key.public_to_pem * * === 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 password. * * cipher = OpenSSL::Cipher.new 'aes-256-cbc' * password = 'my secure password goes here' * * key_secure = key.private_to_pem cipher, password * * File.write 'private.secure.pem', key_secure * * OpenSSL::Cipher.ciphers returns a list of available ciphers. * * === Loading a Key * * A key can also be loaded from a file. * * key2 = OpenSSL::PKey.read File.read 'private_key.pem' * key2.public? # => true * key2.private? # => true * * or * * key3 = OpenSSL::PKey.read File.read 'public_key.pem' * key3.public? # => true * key3.private? # => false * * === Loading an Encrypted Key * * \OpenSSL will prompt you for your password when loading an encrypted key. * If you will not be able to type in the password you may provide it when * loading the key: * * key4_pem = File.read 'private.secure.pem' * password = 'my secure password goes here' * key4 = OpenSSL::PKey.read key4_pem, password * * == 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. * * signature = key.sign 'SHA256', 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. * * if key.verify 'SHA256', 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-256-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.new('SHA256') * * 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-256-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.new('SHA256') * * 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 * * == \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.new('SHA1') * * 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 * password = 'my secure password goes here' * * cipher = 'aes-256-cbc' * * open 'ca_key.pem', 'w', 0400 do |io| * io.write ca_key.private_to_pem(cipher, password) * 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.new('SHA1') * * 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.new('SHA1') * * 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.new('SHA1') * * 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 OpenSSL::SSL::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 OpenSSL::SSL::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 = ssl_connection.gets * * response = "I got #{data.dump}" * puts response * * ssl_connection.puts "I got #{data.dump}" * ssl_connection.close * end * * === \SSL client * * An \SSL client is created with a TCP socket and the context. * OpenSSL::SSL::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 OpenSSL::SSL::SSLSocket#close doesn't close the underlying socket * by default. Set OpenSSL::SSL::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 context.ca_file is not set * when verifying peers an OpenSSL::SSL::SSLError will be raised. * */ void Init_openssl(void) { #ifdef HAVE_RB_EXT_RACTOR_SAFE rb_ext_ractor_safe(true); #endif #undef rb_intern /* * Init timezone info */ #if 0 tzset(); #endif /* * Init all digests, ciphers */ if (!OPENSSL_init_ssl(0, NULL)) rb_raise(rb_eRuntimeError, "OPENSSL_init_ssl"); /* * Init main module */ rb_global_variable(&mOSSL); mOSSL = rb_define_module("OpenSSL"); rb_define_singleton_method(mOSSL, "fixed_length_secure_compare", ossl_crypto_fixed_length_secure_compare, 2); /* * \OpenSSL library version string used to compile the Ruby/OpenSSL * extension. This may differ from the version used at runtime. */ rb_define_const(mOSSL, "OPENSSL_VERSION", rb_obj_freeze(rb_str_new_cstr(OPENSSL_VERSION_TEXT))); /* * \OpenSSL library version string currently used at runtime. */ rb_define_const( mOSSL, "OPENSSL_LIBRARY_VERSION", rb_obj_freeze(rb_str_new_cstr(OpenSSL_version(OPENSSL_VERSION))) ); /* * \OpenSSL library version number used to compile the Ruby/OpenSSL * extension. This may differ from the version used at runtime. * * The version number is encoded into a single integer value. The number * follows the format: * * [\OpenSSL 3.0.0 or later] * 0xMNN00PP0 (major minor 00 patch 0) * [\OpenSSL 1.1.1 or earlier] * 0xMNNFFPPS (major minor fix patch status) * [LibreSSL] * 0x20000000 (a fixed value) * * See also the man page OPENSSL_VERSION_NUMBER(3). */ rb_define_const(mOSSL, "OPENSSL_VERSION_NUMBER", INT2NUM(OPENSSL_VERSION_NUMBER)); #if defined(LIBRESSL_VERSION_NUMBER) /* * LibreSSL library version number used to compile the Ruby/OpenSSL * extension. This may differ from the version used at runtime. * * This constant is only defined if the extension was compiled against * LibreSSL. The number follows the format: * 0xMNNFF00f (major minor fix 00 status). * * See also the man page LIBRESSL_VERSION_NUMBER(3). */ rb_define_const(mOSSL, "LIBRESSL_VERSION_NUMBER", INT2NUM(LIBRESSL_VERSION_NUMBER)); #endif /* * Boolean indicating whether the \OpenSSL library is FIPS-capable or not. * Always true for \OpenSSL 3.0 and later. * * This is obsolete and will be removed in the future. * See also OpenSSL.fips_mode. */ rb_define_const(mOSSL, "OPENSSL_FIPS", /* OpenSSL 3 is FIPS-capable even when it is installed without fips option */ #if OSSL_OPENSSL_PREREQ(3, 0, 0) Qtrue #elif defined(OPENSSL_FIPS) Qtrue #elif defined(OPENSSL_IS_AWSLC) // AWS-LC FIPS can only be enabled during compile time. FIPS_mode() ? Qtrue : Qfalse #else Qfalse #endif ); rb_define_module_function(mOSSL, "fips_mode", ossl_fips_mode_get, 0); rb_define_module_function(mOSSL, "fips_mode=", ossl_fips_mode_set, 1); rb_global_variable(&eOSSLError); /* * Generic error class for OpenSSL. All error classes in this library * inherit from this class. * * This class indicates that an error was reported by the underlying * \OpenSSL library. */ eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); /* * \OpenSSL error queue entries captured at the time the exception was * raised. The same information is printed to stderr if OpenSSL.debug is * set to +true+. * * This is an array of zero or more strings, ordered from the oldest to the * newest. The format of the strings is not stable and may vary across * versions of \OpenSSL or versions of this Ruby extension. * * See also the man page ERR_get_error(3). */ rb_attr(eOSSLError, rb_intern_const("errors"), 1, 0, 0); rb_define_method(eOSSLError, "detailed_message", osslerror_detailed_message, -1); /* * 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"); id_i_errors = rb_intern("@errors"); /* * Init components */ Init_ossl_asn1(); Init_ossl_bn(); Init_ossl_cipher(); Init_ossl_config(); Init_ossl_digest(); Init_ossl_engine(); Init_ossl_hmac(); Init_ossl_kdf(); Init_ossl_ns_spki(); Init_ossl_ocsp(); Init_ossl_pkcs12(); Init_ossl_pkcs7(); Init_ossl_pkey(); Init_ossl_provider(); Init_ossl_rand(); Init_ossl_ssl(); Init_ossl_ts(); Init_ossl_x509(); } ================================================ FILE: ext/openssl/ossl.h ================================================ /* * 'OpenSSL for Ruby' project * Copyright (C) 2001-2002 Michal Rokos * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #if !defined(_OSSL_H_) #define _OSSL_H_ #include RUBY_EXTCONF_H #include #include #include #include #include #ifdef HAVE_RUBY_RACTOR_H #include #else #define RUBY_TYPED_FROZEN_SHAREABLE 0 #endif #include #include #include #include #include #include #include #include #include #ifndef OPENSSL_NO_TS #include #endif #include #if !defined(OPENSSL_NO_OCSP) # include #endif #include #include #include #include #include #include "openssl_missing.h" #ifndef LIBRESSL_VERSION_NUMBER # define OSSL_IS_LIBRESSL 0 # define OSSL_OPENSSL_PREREQ(maj, min, pat) \ (OPENSSL_VERSION_NUMBER >= ((maj << 28) | (min << 20) | (pat << 12))) # define OSSL_LIBRESSL_PREREQ(maj, min, pat) 0 #else # define OSSL_IS_LIBRESSL 1 # define OSSL_OPENSSL_PREREQ(maj, min, pat) 0 # define OSSL_LIBRESSL_PREREQ(maj, min, pat) \ (LIBRESSL_VERSION_NUMBER >= ((maj << 28) | (min << 20) | (pat << 12))) #endif #if OSSL_OPENSSL_PREREQ(3, 0, 0) # define OSSL_3_const const #else # define OSSL_3_const /* const */ #endif #if !defined(OPENSSL_NO_ENGINE) && !OSSL_OPENSSL_PREREQ(3, 0, 0) # define OSSL_USE_ENGINE #endif #if OSSL_OPENSSL_PREREQ(3, 0, 0) # define OSSL_USE_PROVIDER # include #endif #if OSSL_OPENSSL_PREREQ(3, 0, 0) # define OSSL_HAVE_IMMUTABLE_PKEY #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) /* * Type conversions */ #if !defined(NUM2UINT64T) /* in case Ruby starts to provide */ # if SIZEOF_LONG == 8 # define NUM2UINT64T(x) ((uint64_t)NUM2ULONG(x)) # elif defined(HAVE_LONG_LONG) && SIZEOF_LONG_LONG == 8 # define NUM2UINT64T(x) ((uint64_t)NUM2ULL(x)) # else # error "unknown platform; no 64-bit width integer" # endif #endif /* * Data Conversion */ 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); VALUE ossl_str_new(const char *, long, int *); #define ossl_str_adjust(str, p) \ do{\ long newlen = (long)((p) - (unsigned char*)RSTRING_PTR(str));\ assert(newlen <= RSTRING_LEN(str));\ 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(const 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 */ PRINTF_ARGS(NORETURN(void ossl_raise(VALUE, const char *, ...)), 2, 3); /* Make exception instance from str and OpenSSL error reason string. */ VALUE ossl_make_error(VALUE exc, VALUE str); /* Clear OpenSSL error queue. If dOSSL is set, rb_warn() them. */ void ossl_clear_error(void); /* * String to DER String */ VALUE ossl_to_der(VALUE); VALUE ossl_to_der_if_possible(VALUE); /* * Debug */ extern VALUE dOSSL; #define OSSL_Debug(...) do { \ if (dOSSL == Qtrue) { \ fprintf(stderr, "OSSL_DEBUG: "); \ fprintf(stderr, __VA_ARGS__); \ fprintf(stderr, " [%s:%d]\n", __FILE__, __LINE__); \ } \ } while (0) /* * Include all parts */ #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_engine.h" #include "ossl_hmac.h" #include "ossl_kdf.h" #include "ossl_ns_spki.h" #include "ossl_ocsp.h" #include "ossl_pkcs12.h" #include "ossl_pkcs7.h" #include "ossl_pkey.h" #include "ossl_provider.h" #include "ossl_rand.h" #include "ossl_ssl.h" #include "ossl_ts.h" #include "ossl_x509.h" void Init_openssl(void); #endif /* _OSSL_H_ */ ================================================ FILE: ext/openssl/ossl_asn1.c ================================================ /* * 'OpenSSL for Ruby' team members * Copyright (C) 2003 * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #include "ossl.h" /********/ /* * 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_indefinite_length(o) rb_attr_get((o),sivINDEFINITE_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_indefinite_length(o,v) rb_ivar_set((o),sivINDEFINITE_LENGTH,(v)) VALUE mASN1; static VALUE eASN1Error; VALUE cASN1Data; static VALUE cASN1Primitive; static VALUE cASN1Constructive; static VALUE cASN1EndOfContent; static VALUE cASN1Boolean; /* BOOLEAN */ static VALUE cASN1Integer, cASN1Enumerated; /* INTEGER */ static VALUE cASN1BitString; /* BIT STRING */ static VALUE cASN1OctetString, cASN1UTF8String; /* STRINGs */ static VALUE cASN1NumericString, cASN1PrintableString; static VALUE cASN1T61String, cASN1VideotexString; static VALUE cASN1IA5String, cASN1GraphicString; static VALUE cASN1ISO64String, cASN1GeneralString; static VALUE cASN1UniversalString, cASN1BMPString; static VALUE cASN1Null; /* NULL */ static VALUE cASN1ObjectId; /* OBJECT IDENTIFIER */ static VALUE cASN1UTCTime, cASN1GeneralizedTime; /* TIME */ static 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, sivINDEFINITE_LENGTH, sivUNUSED_BITS; static ID id_each; /* * DATE conversion */ static VALUE time_utc_new(VALUE args) { return rb_funcallv(rb_cTime, rb_intern("utc"), 6, (VALUE *)args); } static VALUE time_utc_new_rescue(VALUE args, VALUE exc) { rb_raise(eASN1Error, "invalid time"); } VALUE asn1time_to_time(const ASN1_TIME *time) { struct tm tm; if (!ASN1_TIME_to_tm(time, &tm)) ossl_raise(eASN1Error, "ASN1_TIME_to_tm"); VALUE args[] = { INT2NUM(tm.tm_year + 1900), INT2NUM(tm.tm_mon + 1), INT2NUM(tm.tm_mday), INT2NUM(tm.tm_hour), INT2NUM(tm.tm_min), INT2NUM(tm.tm_sec), }; return rb_rescue2(time_utc_new, (VALUE)args, time_utc_new_rescue, Qnil, rb_eArgError, 0); } static VALUE asn1time_to_time_i(VALUE arg) { return asn1time_to_time((ASN1_TIME *)arg); } 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))); } } /* * STRING conversion */ VALUE asn1str_to_str(const ASN1_STRING *str) { return rb_str_new((const char *)ASN1_STRING_get0_data(str), ASN1_STRING_length(str)); } /* * 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!"); } num = ossl_bn_new(BN_value_one()); bn = GetBNPtr(num); if (ASN1_STRING_type(ai) == V_ASN1_ENUMERATED) bn = ASN1_ENUMERATED_to_BN(ai, bn); else bn = ASN1_INTEGER_to_BN(ai, bn); if (!bn) ossl_raise(eOSSLError, NULL); 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; } static VALUE asn1integer_to_num_i(VALUE arg) { return asn1integer_to_num((ASN1_INTEGER *)arg); } /* * ASN1_OBJECT conversions */ VALUE ossl_asn1obj_to_string_oid(const ASN1_OBJECT *a1obj) { VALUE str; int len; str = rb_usascii_str_new(NULL, 127); len = OBJ_obj2txt(RSTRING_PTR(str), RSTRING_LENINT(str), a1obj, 1); if (len <= 0 || len == INT_MAX) ossl_raise(eOSSLError, "OBJ_obj2txt"); if (len > RSTRING_LEN(str)) { /* +1 is for the \0 terminator added by OBJ_obj2txt() */ rb_str_resize(str, len + 1); len = OBJ_obj2txt(RSTRING_PTR(str), len + 1, a1obj, 1); if (len <= 0) ossl_raise(eOSSLError, "OBJ_obj2txt"); } rb_str_set_len(str, len); return str; } VALUE ossl_asn1obj_to_string(const ASN1_OBJECT *obj) { int nid = OBJ_obj2nid(obj); if (nid != NID_undef) return rb_str_new_cstr(OBJ_nid2sn(nid)); return ossl_asn1obj_to_string_oid(obj); } VALUE ossl_asn1obj_to_string_long_name(const ASN1_OBJECT *obj) { int nid = OBJ_obj2nid(obj); if (nid != NID_undef) return rb_str_new_cstr(OBJ_nid2ln(nid)); return ossl_asn1obj_to_string_oid(obj); } /* * 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, int unused_bits) { ASN1_BIT_STRING *bstr; if (unused_bits < 0 || unused_bits > 7) ossl_raise(eASN1Error, "unused_bits for a bitstring value must be in "\ "the range 0 to 7"); StringValue(obj); if (!(bstr = ASN1_BIT_STRING_new())) ossl_raise(eASN1Error, "ASN1_BIT_STRING_new"); if (!ASN1_BIT_STRING_set1(bstr, (uint8_t *)RSTRING_PTR(obj), RSTRING_LEN(obj), unused_bits)) ossl_raise(eASN1Error, "ASN1_BIT_STRING_set1"); 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; } ASN1_OBJECT * ossl_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; int off_days; ossl_time_split(time, &sec, &off_days); if (!(t = ASN1_UTCTIME_adj(NULL, sec, off_days, 0))) ossl_raise(eASN1Error, NULL); return t; } static ASN1_GENERALIZEDTIME * obj_to_asn1gtime(VALUE time) { time_t sec; ASN1_GENERALIZEDTIME *t; int off_days; ossl_time_split(time, &sec, &off_days); if (!(t = ASN1_GENERALIZEDTIME_adj(NULL, sec, off_days, 0))) 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(asn1integer_to_num_i, (VALUE)ai, &status); ASN1_INTEGER_free(ai); if(status) rb_jump_tag(status); return ret; } static VALUE decode_bstr(unsigned char* der, long length, int *unused_bits) { ASN1_BIT_STRING *bstr; const unsigned char *p; size_t len; VALUE ret; int state; p = der; if (!(bstr = d2i_ASN1_BIT_STRING(NULL, &p, length))) ossl_raise(eASN1Error, "d2i_ASN1_BIT_STRING"); if (!ASN1_BIT_STRING_get_length(bstr, &len, unused_bits)) { ASN1_BIT_STRING_free(bstr); ossl_raise(eASN1Error, "ASN1_BIT_STRING_get_length"); } ret = ossl_str_new((const char *)ASN1_STRING_get0_data(bstr), len, &state); ASN1_BIT_STRING_free(bstr); if (state) rb_jump_tag(state); 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(asn1integer_to_num_i, (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; } VALUE asn1obj_to_string_i(VALUE arg) { return ossl_asn1obj_to_string((const ASN1_OBJECT *)arg); } static VALUE decode_obj(unsigned char* der, long length) { ASN1_OBJECT *obj; const unsigned char *p; VALUE ret; int state; p = der; if (!(obj = d2i_ASN1_OBJECT(NULL, &p, length))) ossl_raise(eASN1Error, "d2i_ASN1_OBJECT"); ret = rb_protect(asn1obj_to_string_i, (VALUE)obj, &state); ASN1_OBJECT_free(obj); if (state) rb_jump_tag(state); 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(asn1time_to_time_i, (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); static ASN1_TYPE * ossl_asn1_get_asn1type(VALUE obj) { ASN1_TYPE *ret; VALUE value, rflag; void *ptr; typedef void free_func_type(void *); free_func_type *free_func; int tag; 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 = (free_func_type *)ASN1_INTEGER_free; break; case V_ASN1_BIT_STRING: rflag = rb_attr_get(obj, sivUNUSED_BITS); ptr = obj_to_asn1bstr(value, NUM2INT(rflag)); free_func = (free_func_type *)ASN1_BIT_STRING_free; break; case V_ASN1_NULL: ptr = obj_to_asn1null(value); free_func = (free_func_type *)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 = (free_func_type *)ASN1_STRING_free; break; case V_ASN1_OBJECT: ptr = ossl_to_asn1obj(value); free_func = (free_func_type *)ASN1_OBJECT_free; break; case V_ASN1_UTCTIME: ptr = obj_to_asn1utime(value); free_func = (free_func_type *)ASN1_TIME_free; break; case V_ASN1_GENERALIZEDTIME: ptr = obj_to_asn1gtime(value); free_func = (free_func_type *)ASN1_TIME_free; break; case V_ASN1_SET: /* FALLTHROUGH */ case V_ASN1_SEQUENCE: ptr = obj_to_asn1derstr(obj); free_func = (free_func_type *)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); } return -1; } 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_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_: An Integer 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"); ossl_asn1_set_tag(self, tag); ossl_asn1_set_value(self, value); ossl_asn1_set_tag_class(self, tag_class); ossl_asn1_set_indefinite_length(self, Qfalse); return self; } static VALUE to_der_internal(VALUE self, int constructed, int indef_len, VALUE body) { int encoding = constructed ? indef_len ? 2 : 1 : 0; int tag_class = ossl_asn1_tag_class(self); int tag_number = ossl_asn1_tag(self); int default_tag_number = ossl_asn1_default_tag(self); int body_length, total_length; VALUE str; unsigned char *p; body_length = RSTRING_LENINT(body); if (ossl_asn1_get_tagging(self) == sym_EXPLICIT) { int inner_length, e_encoding = indef_len ? 2 : 1; if (default_tag_number == -1) ossl_raise(eASN1Error, "explicit tagging of unknown tag"); inner_length = ASN1_object_size(encoding, body_length, default_tag_number); total_length = ASN1_object_size(e_encoding, inner_length, tag_number); str = rb_str_new(NULL, total_length); p = (unsigned char *)RSTRING_PTR(str); /* Put explicit tag */ ASN1_put_object(&p, e_encoding, inner_length, tag_number, tag_class); /* Append inner object */ ASN1_put_object(&p, encoding, body_length, default_tag_number, V_ASN1_UNIVERSAL); memcpy(p, RSTRING_PTR(body), body_length); p += body_length; if (indef_len) { ASN1_put_eoc(&p); /* For inner object */ ASN1_put_eoc(&p); /* For wrapper object */ } } else { total_length = ASN1_object_size(encoding, body_length, tag_number); str = rb_str_new(NULL, total_length); p = (unsigned char *)RSTRING_PTR(str); ASN1_put_object(&p, encoding, body_length, tag_number, tag_class); memcpy(p, RSTRING_PTR(body), body_length); p += body_length; if (indef_len) ASN1_put_eoc(&p); } assert(p - (unsigned char *)RSTRING_PTR(str) == total_length); return str; } static VALUE ossl_asn1prim_to_der(VALUE); static VALUE ossl_asn1cons_to_der(VALUE); /* * 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 indefinite length forms. * Indefinite length forms 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 = ossl_asn1_get_value(self); if (rb_obj_is_kind_of(value, rb_cArray)) return ossl_asn1cons_to_der(self); else { if (RTEST(ossl_asn1_get_indefinite_length(self))) ossl_raise(eASN1Error, "indefinite length form cannot be used " \ "with primitive encoding"); return ossl_asn1prim_to_der(self); } } static VALUE ossl_asn1_initialize(int argc, VALUE *argv, VALUE self); static VALUE ossl_asn1_decode0(unsigned char **pp, long length, long *offset, int depth, int yield, long *num_read); 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; int 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, INT2NUM(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 indefinite; long available_len, off = *offset; indefinite = (j == 0x21); ary = rb_ary_new(); available_len = indefinite ? 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; if (indefinite) { if (ossl_asn1_tag(value) == V_ASN1_EOC && ossl_asn1_get_tag_class(value) == sym_UNIVERSAL) break; if (available_len == 0) ossl_raise(eASN1Error, "EOC missing in indefinite length encoding"); } rb_ary_push(ary, value); } if (tc == sym_UNIVERSAL) { VALUE args[4]; if (tag == V_ASN1_SEQUENCE || tag == V_ASN1_SET) asn1data = rb_obj_alloc(*ossl_asn1_info[tag].klass); else asn1data = rb_obj_alloc(cASN1Constructive); 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 (indefinite) ossl_asn1_set_indefinite_length(asn1data, Qtrue); else ossl_asn1_set_indefinite_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, "indefinite 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 (Integer) * * offset: Current byte offset (Integer) * * header length: Combined length in bytes of the Tag and Length headers. (Integer) * * length: The overall remaining length of the entire data (Integer) * * constructed: Whether this value is constructed or not (Boolean) * * tag_class: Current tag class (Symbol) * * tag: The current tag number (Integer) * * == 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; int default_tag; rb_scan_args(argc, argv, "13", &value, &tag, &tagging, &tag_class); default_tag = ossl_asn1_default_tag(self); if (default_tag == -1 || 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"); } else{ tag = INT2NUM(default_tag); 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_indefinite_length(self, Qfalse); if (default_tag == V_ASN1_BIT_STRING) rb_ivar_set(self, sivUNUSED_BITS, INT2FIX(0)); return self; } static VALUE ossl_asn1eoc_initialize(VALUE self) { VALUE tag, tagging, tag_class, value; tag = INT2FIX(0); 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_indefinite_length(self, Qfalse); return self; } static VALUE ossl_asn1eoc_to_der(VALUE self) { return rb_str_new("\0\0", 2); } /* * 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; long alllen, bodylen; unsigned char *p0, *p1; int j, tag, tc, state; VALUE str; if (ossl_asn1_default_tag(self) == -1) { str = ossl_asn1_get_value(self); return to_der_internal(self, 0, 0, StringValue(str)); } asn1 = ossl_asn1_get_asn1type(self); alllen = i2d_ASN1_TYPE(asn1, NULL); if (alllen < 0) { ASN1_TYPE_free(asn1); ossl_raise(eASN1Error, "i2d_ASN1_TYPE"); } str = ossl_str_new(NULL, alllen, &state); if (state) { ASN1_TYPE_free(asn1); rb_jump_tag(state); } p0 = p1 = (unsigned char *)RSTRING_PTR(str); if (i2d_ASN1_TYPE(asn1, &p0) < 0) { ASN1_TYPE_free(asn1); ossl_raise(eASN1Error, "i2d_ASN1_TYPE"); } ASN1_TYPE_free(asn1); ossl_str_adjust(str, p0); /* Strip header since to_der_internal() wants only the payload */ j = ASN1_get_object((const unsigned char **)&p1, &bodylen, &tag, &tc, alllen); if (j & 0x80) ossl_raise(eASN1Error, "ASN1_get_object"); /* should not happen */ return to_der_internal(self, 0, 0, rb_str_drop_bytes(str, alllen - bodylen)); } /* * call-seq: * asn1.to_der => DER-encoded String * * See ASN1Data#to_der for details. */ static VALUE ossl_asn1cons_to_der(VALUE self) { VALUE ary, str; long i; int indef_len; indef_len = RTEST(ossl_asn1_get_indefinite_length(self)); ary = rb_convert_type(ossl_asn1_get_value(self), T_ARRAY, "Array", "to_a"); str = rb_str_new(NULL, 0); for (i = 0; i < RARRAY_LEN(ary); i++) { VALUE item = RARRAY_AREF(ary, i); if (indef_len && rb_obj_is_kind_of(item, cASN1EndOfContent)) { if (i != RARRAY_LEN(ary) - 1) ossl_raise(eASN1Error, "illegal EOC octets in value"); /* * EOC is not really part of the content, but we required to add one * at the end in the past. */ break; } item = ossl_to_der_if_possible(item); StringValue(item); rb_str_append(str, item); } return to_der_internal(self, 1, indef_len, str); } /* * call-seq: * asn1_ary.each { |asn1| block } => asn1_ary * * Calls the given block 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; } /* * call-seq: * oid.sn -> string * oid.short_name -> string * * The short name of the ObjectId, as defined in . */ 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; } /* * call-seq: * oid.ln -> string * oid.long_name -> string * * The long name of the ObjectId, as defined in . */ 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; } static VALUE asn1obj_get_oid_i(VALUE vobj) { return ossl_asn1obj_to_string_oid((const ASN1_OBJECT *)vobj); } /* * call-seq: * oid.oid -> string * * Returns a String representing the Object Identifier in the dot notation, * e.g. "1.2.3.4.5" */ static VALUE ossl_asn1obj_get_oid(VALUE self) { VALUE str; ASN1_OBJECT *a1obj; int state; a1obj = ossl_to_asn1obj(ossl_asn1_get_value(self)); str = rb_protect(asn1obj_get_oid_i, (VALUE)a1obj, &state); ASN1_OBJECT_free(a1obj); if (state) rb_jump_tag(state); return str; } /* * call-seq: * oid == other_oid => true or false * * Returns +true+ if _other_oid_ is the same as _oid_. */ static VALUE ossl_asn1obj_eq(VALUE self, VALUE other) { VALUE oid1, oid2; if (!rb_obj_is_kind_of(other, cASN1ObjectId)) return Qfalse; oid1 = ossl_asn1obj_get_oid(self); oid2 = ossl_asn1obj_get_oid(other); return rb_str_equal(oid1, oid2); } #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) { #undef rb_intern 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"); sivINDEFINITE_LENGTH = rb_intern("@indefinite_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. 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); VALUE ary = rb_ary_new_capa(ossl_asn1_info_size); for (int i = 0; i < ossl_asn1_info_size; i++) { const char *name = ossl_asn1_info[i].name; if (name[0] == '[') continue; rb_define_const(mASN1, name, INT2NUM(i)); rb_ary_store(ary, i, rb_obj_freeze(rb_str_new_cstr(name))); } rb_obj_freeze(ary); /* * Array storing tag names at the tag's index. */ rb_define_const(mASN1, "UNIVERSAL_TAG_NAME", ary); /* 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 => #]> * 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 => #]>]> * 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); /* * An Integer 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 value indicating whether the encoding uses * indefinite length (in the case of parsing) or whether an indefinite * length form shall be used (in the encoding case). * In DER, every value uses definite length form. 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. Indefinite 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 indefinite 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("indefinite_length"), 1, 1, 0); rb_define_alias(cASN1Data, "infinite_length", "indefinite_length"); rb_define_alias(cASN1Data, "infinite_length=", "indefinite_length="); 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 encoded with indefinite length form, thus * it is not possible to set the _indefinite_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 +true+ or +false+ * * OpenSSL::ASN1::Integer <=> _value_ is an OpenSSL::BN * * 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 an OpenSSL::BN * * 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 . * * _ln_: the long name as defined in . * * _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 = .new(value) # being one of the sub-classes except EndOfContent * prim_zero_tagged_implicit = .new(value, 0, :IMPLICIT) * prim_zero_tagged_explicit = .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, "indefinite_length="); 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 ] ) */ 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_define_method(cASN1ObjectId, "==", ossl_asn1obj_eq, 1); rb_attr(cASN1BitString, rb_intern("unused_bits"), 1, 1, 0); rb_define_method(cASN1EndOfContent, "initialize", ossl_asn1eoc_initialize, 0); rb_define_method(cASN1EndOfContent, "to_der", ossl_asn1eoc_to_der, 0); class_tag_map = rb_hash_new(); rb_gc_register_mark_object(class_tag_map); 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_obj_freeze(class_tag_map); id_each = rb_intern_const("each"); } ================================================ FILE: ext/openssl/ossl_asn1.h ================================================ /* * 'OpenSSL for Ruby' team members * Copyright (C) 2003 * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #if !defined(_OSSL_ASN1_H_) #define _OSSL_ASN1_H_ /* * ASN1_DATE conversions */ VALUE asn1time_to_time(const ASN1_TIME *); /* 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 *); /* * 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_OBJECT conversions */ ASN1_OBJECT *ossl_to_asn1obj(VALUE obj); /* * Returns the short name if available, the dotted decimal notation otherwise. * This is the most common way to return ASN1_OBJECT to Ruby. */ VALUE ossl_asn1obj_to_string(const ASN1_OBJECT *a1obj); /* * However, some places use long names instead. This is likely unintentional, * but we keep the current behavior in existing methods. */ VALUE ossl_asn1obj_to_string_long_name(const ASN1_OBJECT *a1obj); /* * ASN1 module */ extern VALUE mASN1; extern VALUE cASN1Data; void Init_ossl_asn1(void); #endif ================================================ FILE: ext/openssl/ossl_bio.c ================================================ /* * 'OpenSSL for Ruby' team members * Copyright (C) 2003 * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #include "ossl.h" BIO * ossl_obj2bio(volatile VALUE *pobj) { VALUE obj = *pobj; BIO *bio; if (RB_TYPE_P(obj, T_FILE)) obj = rb_funcallv(obj, rb_intern("read"), 0, NULL); StringValue(obj); bio = BIO_new_mem_buf(RSTRING_PTR(obj), RSTRING_LENINT(obj)); if (!bio) ossl_raise(eOSSLError, "BIO_new_mem_buf"); *pobj = obj; return bio; } VALUE ossl_membio2str(BIO *bio) { VALUE ret; int state; BUF_MEM *buf; if (BIO_get_mem_ptr(bio, &buf) <= 0) { BIO_free(bio); ossl_raise(eOSSLError, "BIO_get_mem_ptr"); } ret = ossl_str_new(buf->data, buf->length, &state); BIO_free(bio); if (state) rb_jump_tag(state); return ret; } ================================================ FILE: ext/openssl/ossl_bio.h ================================================ /* * 'OpenSSL for Ruby' team members * Copyright (C) 2003 * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #if !defined(_OSSL_BIO_H_) #define _OSSL_BIO_H_ BIO *ossl_obj2bio(volatile VALUE *); VALUE ossl_membio2str(BIO*); #endif ================================================ FILE: ext/openssl/ossl_bn.c ================================================ /* * 'OpenSSL for Ruby' project * Copyright (C) 2001-2002 Technorama team * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ /* modified by Michal Rokos */ #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) 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 | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FROZEN_SHAREABLE, }; /* * Classes */ VALUE cBN; /* Document-class: OpenSSL::BNError * * Generic Error for all of OpenSSL::BN (big num) */ static VALUE eBNError; /* * Public */ VALUE ossl_bn_new(const BIGNUM *bn) { BIGNUM *newbn; VALUE obj; obj = NewBN(cBN); newbn = BN_dup(bn); if (!newbn) ossl_raise(eBNError, "BN_dup"); 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 */ #ifdef HAVE_RB_EXT_RACTOR_SAFE static void ossl_bn_ctx_free(void *ptr) { BN_CTX *ctx = (BN_CTX *)ptr; BN_CTX_free(ctx); } static struct rb_ractor_local_storage_type ossl_bn_ctx_key_type = { NULL, // mark ossl_bn_ctx_free, }; static rb_ractor_local_key_t ossl_bn_ctx_key; BN_CTX * ossl_bn_ctx_get(void) { // stored in ractor local storage BN_CTX *ctx = rb_ractor_local_storage_ptr(ossl_bn_ctx_key); if (!ctx) { if (!(ctx = BN_CTX_new())) { ossl_raise(rb_eRuntimeError, "Cannot init BN_CTX"); } rb_ractor_local_storage_ptr_set(ossl_bn_ctx_key, ctx); } return ctx; } #else // for ruby 2.x static BN_CTX *gv_ossl_bn_ctx; BN_CTX * ossl_bn_ctx_get(void) { if (gv_ossl_bn_ctx == NULL) { if (!(gv_ossl_bn_ctx = BN_CTX_new())) { ossl_raise(rb_eRuntimeError, "Cannot init BN_CTX"); } } return gv_ossl_bn_ctx; } void ossl_bn_ctx_free(void) { BN_CTX_free(gv_ossl_bn_ctx); gv_ossl_bn_ctx = NULL; } #endif 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; } /* * call-seq: * OpenSSL::BN.new(bn) -> aBN * OpenSSL::BN.new(integer) -> aBN * OpenSSL::BN.new(string, base = 10) -> aBN * * Construct a new \OpenSSL BIGNUM object. * * If +bn+ is an Integer or OpenSSL::BN, a new instance of OpenSSL::BN * representing the same value is returned. See also Integer#to_bn for the * short-hand. * * If a String is given, the content will be parsed according to +base+. * * +string+:: * The string to be parsed. * +base+:: * The format. Must be one of the following: * - +0+ - MPI format. See the man page BN_mpi2bn(3) for details. * - +2+ - Variable-length and big-endian binary encoding of a positive * number. * - +10+ - Decimal number representation, with a leading '-' for a negative * number. * - +16+ - Hexadecimal number representation, with a leading '-' for a * negative number. */ static VALUE ossl_bn_initialize(int argc, VALUE *argv, VALUE self) { BIGNUM *bn; VALUE str, bs; int base = 10; char *ptr; if (rb_scan_args(argc, argv, "11", &str, &bs) == 2) { base = NUM2INT(bs); } if (NIL_P(str)) { ossl_raise(rb_eArgError, "invalid argument"); } rb_check_frozen(self); 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: ptr = StringValuePtr(str); if (!BN_mpi2bn((unsigned char *)ptr, RSTRING_LENINT(str), bn)) { ossl_raise(eBNError, NULL); } break; case 2: ptr = StringValuePtr(str); if (!BN_bin2bn((unsigned char *)ptr, 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(base = 10) -> string * * Returns the string representation of the bignum. * * BN.new can parse the encoded string to convert back into an OpenSSL::BN. * * +base+:: * The format. Must be one of the following: * - +0+ - MPI format. See the man page BN_bn2mpi(3) for details. * - +2+ - Variable-length and big-endian binary encoding. The sign of * the bignum is ignored. * - +10+ - Decimal number representation, with a leading '-' for a negative * bignum. * - +16+ - Hexadecimal number representation, with a leading '-' for a * negative bignum. */ 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) /* * call-seq: * bn.negative? => true | false */ static VALUE ossl_bn_is_negative(VALUE self) { BIGNUM *bn; GetBN(self, bn); if (BN_is_zero(bn)) return Qfalse; return BN_is_negative(bn) ? Qtrue : Qfalse; } #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) <= 0) { \ 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) <= 0) { \ 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) <= 0) { \ 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) #define BIGNUM_2cr(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_##func(NULL, bn1, bn2, ossl_bn_ctx))) \ ossl_raise(eBNError, NULL); \ SetBN(obj, result); \ return obj; \ } /* * Document-method: OpenSSL::BN#mod_sqrt * call-seq: * bn.mod_sqrt(bn2) => aBN */ BIGNUM_2cr(mod_sqrt) /* * Document-method: OpenSSL::BN#mod_inverse * call-seq: * bn.mod_inverse(bn2) => aBN */ BIGNUM_2cr(mod_inverse) /* * 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) <= 0) { \ 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; \ rb_check_frozen(self); \ GetBN(self, bn); \ if (BN_##func(bn, NUM2INT(bit)) <= 0) { \ 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) /* * call-seq: * bn.bit_set?(bit) => true | false * * Tests bit _bit_ in _bn_ and returns +true+ if set, +false+ if not set. */ 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) <= 0) { \ 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; \ rb_check_frozen(self); \ b = NUM2INT(bits); \ GetBN(self, bn); \ if (BN_##func(bn, bn, b) <= 0) \ 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) /* * call-seq: * BN.rand(bits [, fill [, odd]]) -> aBN * * Generates a cryptographically strong pseudo-random number of +bits+. * * See also the man page BN_rand(3). */ static VALUE ossl_bn_s_rand(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, "BN_new"); } if (BN_rand(result, b, top, bottom) <= 0) { BN_free(result); ossl_raise(eBNError, "BN_rand"); } SetBN(obj, result); return obj; } /* * call-seq: * BN.rand_range(range) -> aBN * * Generates a cryptographically strong pseudo-random number in the range * 0...+range+. * * See also the man page BN_rand_range(3). */ static VALUE ossl_bn_s_rand_range(VALUE klass, VALUE range) { BIGNUM *bn = GetBNPtr(range), *result; VALUE obj = NewBN(klass); if (!(result = BN_new())) ossl_raise(eBNError, "BN_new"); if (BN_rand_range(result, bn) <= 0) { BN_free(result); ossl_raise(eBNError, "BN_rand_range"); } SetBN(obj, result); return obj; } /* * call-seq: * BN.generate_prime(bits, [, safe [, add [, rem]]]) => bn * * Generates a random prime number of bit length _bits_. If _safe_ is set to * +true+, generates a safe prime. If _add_ is specified, generates a prime that * fulfills condition p % add = rem. * * === 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) /* :nodoc: */ 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; } /* * call-seq: * +bn -> aBN */ static VALUE ossl_bn_uplus(VALUE self) { VALUE obj; BIGNUM *bn1, *bn2; GetBN(self, bn1); obj = NewBN(cBN); bn2 = BN_dup(bn1); if (!bn2) ossl_raise(eBNError, "BN_dup"); SetBN(obj, bn2); return obj; } /* * call-seq: * -bn -> aBN */ static VALUE ossl_bn_uminus(VALUE self) { VALUE obj; BIGNUM *bn1, *bn2; GetBN(self, bn1); obj = NewBN(cBN); bn2 = BN_dup(bn1); if (!bn2) ossl_raise(eBNError, "BN_dup"); SetBN(obj, bn2); BN_set_negative(bn2, !BN_is_negative(bn2)); return obj; } /* * call-seq: * bn.abs -> aBN */ static VALUE ossl_bn_abs(VALUE self) { BIGNUM *bn1; GetBN(self, bn1); if (BN_is_negative(bn1)) { return ossl_bn_uminus(self); } else { return ossl_bn_uplus(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 true only if obj is a * OpenSSL::BN with the same value as bn. 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 tmp, hash; unsigned char *buf; int len; GetBN(self, bn); len = BN_num_bytes(bn); buf = ALLOCV(tmp, len); if (BN_bn2bin(bn, buf) != len) { ALLOCV_END(tmp); ossl_raise(eBNError, "BN_bn2bin"); } hash = ST2FIX(rb_memhash(buf, len)); ALLOCV_END(tmp); return hash; } /* * call-seq: * bn.prime? => true | false * bn.prime?(checks) => true | false * * Performs a Miller-Rabin probabilistic primality test for +bn+. * * +checks+ parameter is deprecated in version 3.0. It has no effect. */ static VALUE ossl_bn_is_prime(int argc, VALUE *argv, VALUE self) { BIGNUM *bn; int ret; rb_check_arity(argc, 0, 1); GetBN(self, bn); #ifdef HAVE_BN_CHECK_PRIME ret = BN_check_prime(bn, ossl_bn_ctx, NULL); if (ret < 0) ossl_raise(eBNError, "BN_check_prime"); #else ret = BN_is_prime_fasttest_ex(bn, BN_prime_checks, ossl_bn_ctx, 1, NULL); if (ret < 0) ossl_raise(eBNError, "BN_is_prime_fasttest_ex"); #endif return ret ? Qtrue : Qfalse; } /* * 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 probabilistic primality test for +bn+. * * Deprecated in version 3.0. Use #prime? instead. * * +checks+ and +trial_div+ parameters no longer have any effect. */ static VALUE ossl_bn_is_prime_fasttest(int argc, VALUE *argv, VALUE self) { rb_check_arity(argc, 0, 2); return ossl_bn_is_prime(0, argv, self); } /* * call-seq: * bn.get_flags(flags) => flags * * Returns the flags on the BN object. * The argument is used as a bit mask. * * === Parameters * * _flags_ - integer */ static VALUE ossl_bn_get_flags(VALUE self, VALUE arg) { BIGNUM *bn; GetBN(self, bn); return INT2NUM(BN_get_flags(bn, NUM2INT(arg))); } /* * call-seq: * bn.set_flags(flags) => nil * * Enables the flags on the BN object. * Currently, the flags argument can contain zero of OpenSSL::BN::CONSTTIME. */ static VALUE ossl_bn_set_flags(VALUE self, VALUE arg) { BIGNUM *bn; GetBN(self, bn); rb_check_frozen(self); BN_set_flags(bn, NUM2INT(arg)); return Qnil; } /* * INIT * (NOTE: ordering of methods is the same as in 'man bn') */ void Init_ossl_bn(void) { #ifdef HAVE_RB_EXT_RACTOR_SAFE ossl_bn_ctx_key = rb_ractor_local_storage_ptr_newkey(&ossl_bn_ctx_key_type); #else ossl_bn_ctx_get(); #endif 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_method(cBN, "initialize_copy", ossl_bn_copy, 1); 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_uplus, 0); rb_define_method(cBN, "-@", ossl_bn_uminus, 0); rb_define_method(cBN, "abs", ossl_bn_abs, 0); 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, "mod_sqrt", ossl_bn_mod_sqrt, 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); rb_define_method(cBN, "negative?", ossl_bn_is_negative, 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, "rand_range", ossl_bn_s_rand_range, 1); rb_define_alias(rb_singleton_class(cBN), "pseudo_rand", "rand"); rb_define_alias(rb_singleton_class(cBN), "pseudo_rand_range", "rand_range"); 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. */ rb_define_method(cBN, "get_flags", ossl_bn_get_flags, 1); rb_define_method(cBN, "set_flags", ossl_bn_set_flags, 1); #ifdef BN_FLG_CONSTTIME rb_define_const(cBN, "CONSTTIME", INT2NUM(BN_FLG_CONSTTIME)); #endif /* BN_FLG_MALLOCED and BN_FLG_STATIC_DATA seems for C programming. * Allowing them leads to memory leak. * So, for now, they are not exported #ifdef BN_FLG_MALLOCED rb_define_const(cBN, "MALLOCED", INT2NUM(BN_FLG_MALLOCED)); #endif #ifdef BN_FLG_STATIC_DATA rb_define_const(cBN, "STATIC_DATA", INT2NUM(BN_FLG_STATIC_DATA)); #endif */ /* * 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 */ } ================================================ FILE: ext/openssl/ossl_bn.h ================================================ /* * 'OpenSSL for Ruby' project * Copyright (C) 2001-2002 Michal Rokos * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #if !defined(_OSSL_BN_H_) #define _OSSL_BN_H_ extern VALUE cBN; BN_CTX *ossl_bn_ctx_get(void); #define ossl_bn_ctx ossl_bn_ctx_get() #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_ */ ================================================ FILE: ext/openssl/ossl_cipher.c ================================================ /* * 'OpenSSL for Ruby' project * Copyright (C) 2001-2002 Michal Rokos * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #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) /* * Classes */ static VALUE cCipher; static VALUE eCipherError; static VALUE eAuthTagError; static ID id_auth_tag_len, id_key_set, id_cipher_holder; 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 | RUBY_TYPED_WB_PROTECTED, }; #ifdef OSSL_USE_PROVIDER static void ossl_evp_cipher_free(void *ptr) { // This is safe to call against const EVP_CIPHER * returned by // EVP_get_cipherbyname() EVP_CIPHER_free(ptr); } static const rb_data_type_t ossl_evp_cipher_holder_type = { "OpenSSL/EVP_CIPHER", { .dfree = ossl_evp_cipher_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; #endif /* * PUBLIC */ const EVP_CIPHER * ossl_evp_cipher_fetch(VALUE obj, volatile VALUE *holder) { *holder = Qnil; if (rb_obj_is_kind_of(obj, cCipher)) { EVP_CIPHER_CTX *ctx; GetCipher(obj, ctx); EVP_CIPHER *cipher = (EVP_CIPHER *)EVP_CIPHER_CTX_cipher(ctx); #ifdef OSSL_USE_PROVIDER *holder = TypedData_Wrap_Struct(0, &ossl_evp_cipher_holder_type, NULL); if (!EVP_CIPHER_up_ref(cipher)) ossl_raise(eCipherError, "EVP_CIPHER_up_ref"); RTYPEDDATA_DATA(*holder) = cipher; #endif return cipher; } const char *name = StringValueCStr(obj); EVP_CIPHER *cipher = (EVP_CIPHER *)EVP_get_cipherbyname(name); #ifdef OSSL_USE_PROVIDER if (!cipher) { ossl_clear_error(); *holder = TypedData_Wrap_Struct(0, &ossl_evp_cipher_holder_type, NULL); cipher = EVP_CIPHER_fetch(NULL, name, NULL); RTYPEDDATA_DATA(*holder) = cipher; } #endif if (!cipher) ossl_raise(eCipherError, "unsupported cipher algorithm: %"PRIsVALUE, obj); return cipher; } VALUE ossl_cipher_new(const EVP_CIPHER *cipher) { VALUE ret; EVP_CIPHER_CTX *ctx; // NOTE: This does not set id_cipher_holder because this function should // only be called from ossl_engine.c, which will not use any // reference-counted ciphers. 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-256-cbc". * * 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; VALUE cipher_holder; GetCipherInit(self, ctx); if (ctx) { ossl_raise(rb_eRuntimeError, "Cipher already initialized!"); } cipher = ossl_evp_cipher_fetch(str, &cipher_holder); AllocCipher(self, ctx); if (EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, -1) != 1) ossl_raise(eCipherError, "EVP_CipherInit_ex"); rb_ivar_set(self, id_cipher_holder, cipher_holder); return self; } /* :nodoc: */ 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); } GetCipher(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, void *arg) { VALUE ary = (VALUE)arg; rb_ary_push(ary, rb_str_new2(name->name)); } /* * 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, 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(VALUE self, int enc) { EVP_CIPHER_CTX *ctx; GetCipher(self, ctx); if (EVP_CipherInit_ex(ctx, NULL, NULL, NULL, NULL, enc) != 1) { ossl_raise(eCipherError, "EVP_CipherInit_ex"); } rb_ivar_set(self, id_key_set, Qfalse); return self; } /* * call-seq: * cipher.encrypt -> self * * Initializes the Cipher for encryption. * * Make sure to call either #encrypt or #decrypt before using the Cipher for * any operation or setting any parameters. * * Internally calls EVP_CipherInit_ex(ctx, NULL, NULL, NULL, NULL, 1). */ static VALUE ossl_cipher_encrypt(VALUE self) { return ossl_cipher_init(self, 1); } /* * call-seq: * cipher.decrypt -> self * * Initializes the Cipher for decryption. * * Make sure to call either #encrypt or #decrypt before using the Cipher for * any operation or setting any parameters. * * Internally calls EVP_CipherInit_ex(ctx, NULL, NULL, NULL, NULL, 0). */ static VALUE ossl_cipher_decrypt(VALUE self) { return ossl_cipher_init(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 deprecated and should not be used. This method * corresponds to EVP_BytesToKey(), a non-standard OpenSSL extension of the * legacy PKCS #5 v1.5 key derivation function. See OpenSSL::KDF for other * options to derive keys from passwords. * * === 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' */ 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, md_holder; 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); if (iter <= 0) rb_raise(rb_eArgError, "iterations must be a positive integer"); digest = NIL_P(vdigest) ? EVP_md5() : ossl_evp_md_fetch(vdigest, &md_holder); 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. * * *NOTE*: When decrypting using an AEAD cipher, the integrity of the output * is not verified until #final has been called. */ 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); in_len = RSTRING_LEN(data); GetCipher(self, ctx); /* * As of OpenSSL 3.2, there is no reliable way to determine the required * output buffer size for arbitrary cipher modes. * https://github.com/openssl/openssl/issues/22628 * * in_len+block_size is usually sufficient, but AES key wrap with padding * ciphers require in_len+15 even though they have a block size of 8 bytes. * * Using EVP_MAX_BLOCK_LENGTH (32) as a safe upper bound for ciphers * currently implemented in OpenSSL, but this can change in the future. */ if (in_len > LONG_MAX - EVP_MAX_BLOCK_LENGTH) { ossl_raise(rb_eRangeError, "data too big to make output buffer: %ld bytes", in_len); } out_len = in_len + EVP_MAX_BLOCK_LENGTH; if (NIL_P(str)) str = rb_str_buf_new(out_len); else { StringValue(str); if ((long)rb_str_capacity(str) >= out_len) rb_str_modify(str); else rb_str_modify_expand(str, out_len - RSTRING_LEN(str)); } if (!ossl_cipher_update_long(ctx, (unsigned char *)RSTRING_PTR(str), &out_len, in, in_len)) ossl_raise(eCipherError, "EVP_CipherUpdate"); 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 are invalid. 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. * * When encrypting using an AEAD cipher, the authentication tag can be * retrieved by #auth_tag after #final has been called. * * When decrypting using an AEAD cipher, this method will verify the integrity * of the ciphertext and the associated data with the authentication tag, * which must be set by #auth_tag= prior to calling this method. * If the verification fails, CipherError will be raised. */ 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)) { /* For AEAD ciphers, this is likely an authentication failure */ if (EVP_CIPHER_flags(EVP_CIPHER_CTX_cipher(ctx)) & EVP_CIPH_FLAG_AEAD_CIPHER) { /* For AEAD ciphers, EVP_CipherFinal_ex failures are authentication tag verification failures */ ossl_raise(eAuthTagError, "AEAD authentication tag verification failed"); } else { /* For non-AEAD ciphers */ ossl_raise(eCipherError, "cipher final failed"); } } rb_str_set_len(str, out_len); return str; } /* * call-seq: * cipher.name -> string * * Returns the short 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 * * 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. * * See also the man page EVP_CipherInit_ex(3). */ 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 * * 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. * * This method expects the String to have the length equal to #iv_len. To use * a different IV length with an AEAD cipher, #iv_len= must be set prior to * calling this method. * * *NOTE*: In OpenSSL API conventions, the IV value may correspond to the * "nonce" instead in some cipher modes. Refer to the OpenSSL man pages for * details. * * See also the man page EVP_CipherInit_ex(3). */ static VALUE ossl_cipher_set_iv(VALUE self, VALUE iv) { EVP_CIPHER_CTX *ctx; int iv_len = 0; StringValue(iv); GetCipher(self, ctx); if (EVP_CIPHER_flags(EVP_CIPHER_CTX_cipher(ctx)) & EVP_CIPH_FLAG_AEAD_CIPHER) iv_len = (int)(VALUE)EVP_CIPHER_CTX_get_app_data(ctx); 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 * * Indicates whether this Cipher instance uses an AEAD mode. */ static VALUE ossl_cipher_is_authenticated(VALUE self) { EVP_CIPHER_CTX *ctx; GetCipher(self, ctx); return (EVP_CIPHER_flags(EVP_CIPHER_CTX_cipher(ctx)) & EVP_CIPH_FLAG_AEAD_CIPHER) ? Qtrue : Qfalse; } /* * call-seq: * cipher.auth_data = string * * Sets additional authenticated data (AAD), also called associated data, for * this Cipher. This method is available for AEAD ciphers. * * 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. * * This method must be called after #key= and #iv= have been set, but before * starting actual encryption or decryption with #update. In some cipher modes, * #auth_tag_len= and #ccm_data_len= may also need to be called before this * method. * * See also the "AEAD Interface" section of the man page EVP_EncryptInit(3). * This method internally calls EVP_CipherUpdate() with the output buffer * set to NULL. */ 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 (!(EVP_CIPHER_flags(EVP_CIPHER_CTX_cipher(ctx)) & EVP_CIPH_FLAG_AEAD_CIPHER)) ossl_raise(eCipherError, "AEAD not supported by this cipher"); 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 generated authentication tag. This method is available for AEAD * ciphers, and should be called after encryption has been finalized by calling * #final. * * The returned tag will be _tag_len_ bytes long. Some cipher modes require * the desired length in advance using a separate call to #auth_tag_len=, * before starting encryption. * * See also the "AEAD Interface" section of the man page EVP_EncryptInit(3). * This method internally calls EVP_CIPHER_CTX_ctrl() with * EVP_CTRL_AEAD_GET_TAG. */ 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_flags(EVP_CIPHER_CTX_cipher(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 * * Sets the authentication tag to verify the integrity of the ciphertext. * * The authentication tag must be set before #final is called. The tag is * verified during the #final call. * * Note that, for CCM mode and OCB mode, the expected length of the tag must * be set before starting decryption by a separate call to #auth_tag_len=. * The content of the tag can be provided at any time before #final is called. * * *NOTE*: The caller must ensure that the String passed to this method has * the desired length. Some cipher modes support variable tag lengths, and * this method may accept a truncated tag without raising an exception. * * See also the "AEAD Interface" section of the man page EVP_EncryptInit(3). * This method internally calls EVP_CIPHER_CTX_ctrl() with * EVP_CTRL_AEAD_SET_TAG. */ 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_flags(EVP_CIPHER_CTX_cipher(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 * * Sets the length of the expected authentication tag for this Cipher. This * method is available for some of AEAD ciphers that require the length to be * set before starting encryption or decryption, such as CCM mode or OCB mode. * * For CCM mode and OCB mode, the tag length must be set before #iv= is set. * * See also the "AEAD Interface" section of the man page EVP_EncryptInit(3). * This method internally calls EVP_CIPHER_CTX_ctrl() with * EVP_CTRL_AEAD_SET_TAG and a NULL buffer. */ 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_flags(EVP_CIPHER_CTX_cipher(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 * * Sets the IV/nonce length for this Cipher. This method is available for AEAD * ciphers that support variable IV lengths. This method can be called if a * different IV length than OpenSSL's default is desired, prior to calling * #iv=. * * See also the "AEAD Interface" section of the man page EVP_EncryptInit(3). * This method internally calls EVP_CIPHER_CTX_ctrl() with * EVP_CTRL_AEAD_SET_IVLEN. */ 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_flags(EVP_CIPHER_CTX_cipher(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; } /* * call-seq: * cipher.key_len = 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; } // TODO: Should #padding= take a boolean value instead? /* * call-seq: * cipher.padding = 1 or 0 * * 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 (EVP_CIPHER_flags(EVP_CIPHER_CTX_cipher(ctx)) & EVP_CIPH_FLAG_AEAD_CIPHER) len = (int)(VALUE)EVP_CIPHER_CTX_get_app_data(ctx); 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)); } /* * call-seq: * cipher.ccm_data_len = integer * * Sets the total length of the plaintext / ciphertext message that will be * processed by #update in CCM mode. * * Make sure to call this method after #key= and #iv= have been set, and * before #auth_data= or #update are called. * * This method is only available for CCM mode ciphers. * * See also the "AEAD Interface" section of the man page EVP_EncryptInit(3). */ static VALUE ossl_cipher_set_ccm_data_len(VALUE self, VALUE data_len) { int in_len, out_len; EVP_CIPHER_CTX *ctx; in_len = NUM2INT(data_len); GetCipher(self, ctx); if (EVP_CipherUpdate(ctx, NULL, &out_len, NULL, in_len) != 1) ossl_raise(eCipherError, NULL); return data_len; } /* * INIT */ void Init_ossl_cipher(void) { /* 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('--') * * 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') * * === 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::Cipher.new('aes-256-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 * * You should never use ECB mode unless you are absolutely sure that * you absolutely need it * * 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 * * Always create a secure random IV for every encryption of your * Cipher * * 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.new('aes-128-cbc') * cipher.encrypt * key = cipher.random_key * iv = cipher.random_iv * * encrypted = cipher.update(data) + cipher.final * ... * decipher = OpenSSL::Cipher.new('aes-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. * * Associated data, also called additional authenticated data (AAD), is * optionally used where there is additional information, such as * headers or some metadata, that must be also authenticated but not * necessarily need to be encrypted. * * 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. * * key = OpenSSL::Random.random_bytes(16) * nonce = OpenSSL::Random.random_bytes(12) * auth_data = "authenticated but unencrypted data" * data = "encrypted data" * * cipher = OpenSSL::Cipher.new('aes-128-gcm').encrypt * cipher.key = key * cipher.iv = nonce * cipher.auth_data = auth_data * * encrypted = cipher.update(data) + cipher.final * tag = cipher.auth_tag(16) * * 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.new('aes-128-gcm').decrypt * decipher.key = key * decipher.iv = nonce * decipher.auth_tag = tag # could be called at any time before #final * decipher.auth_data = auth_data * * decrypted = decipher.update(encrypted) + decipher.final * * puts data == decrypted #=> true * * Note that other AEAD ciphers may require additional steps, such as * setting the expected tag length (#auth_tag_len=) or the total data * length (#ccm_data_len=) in advance. Make sure to read the relevant man * page for details. */ cCipher = rb_define_class_under(mOSSL, "Cipher", rb_cObject); eCipherError = rb_define_class_under(cCipher, "CipherError", eOSSLError); eAuthTagError = rb_define_class_under(cCipher, "AuthTagError", eCipherError); rb_define_alloc_func(cCipher, ossl_cipher_alloc); rb_define_method(cCipher, "initialize_copy", ossl_cipher_copy, 1); 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, 0); rb_define_method(cCipher, "decrypt", ossl_cipher_decrypt, 0); 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); rb_define_method(cCipher, "ccm_data_len=", ossl_cipher_set_ccm_data_len, 1); id_auth_tag_len = rb_intern_const("auth_tag_len"); id_key_set = rb_intern_const("key_set"); id_cipher_holder = rb_intern_const("EVP_CIPHER_holder"); } ================================================ FILE: ext/openssl/ossl_cipher.h ================================================ /* * 'OpenSSL for Ruby' project * Copyright (C) 2001-2002 Michal Rokos * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #if !defined(_OSSL_CIPHER_H_) #define _OSSL_CIPHER_H_ /* * Gets EVP_CIPHER from a String or an OpenSSL::Digest instance (discouraged, * but still supported for compatibility). A holder object is created if the * EVP_CIPHER is a "fetched" algorithm. */ const EVP_CIPHER *ossl_evp_cipher_fetch(VALUE obj, volatile VALUE *holder); /* * This is meant for OpenSSL::Engine#cipher. EVP_CIPHER must not be a fetched * one. */ VALUE ossl_cipher_new(const EVP_CIPHER *); void Init_ossl_cipher(void); #endif /* _OSSL_CIPHER_H_ */ ================================================ FILE: ext/openssl/ossl_config.c ================================================ /* * 'OpenSSL for Ruby' project * Copyright (C) 2001-2002 Michal Rokos * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #include "ossl.h" static VALUE cConfig, eConfigError; static void nconf_free(void *conf) { NCONF_free(conf); } static const rb_data_type_t ossl_config_type = { "OpenSSL/CONF", { 0, nconf_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FROZEN_SHAREABLE, }; CONF * GetConfig(VALUE obj) { CONF *conf; TypedData_Get_Struct(obj, CONF, &ossl_config_type, conf); if (!conf) rb_raise(rb_eRuntimeError, "CONF is not initialized"); return conf; } static VALUE config_s_alloc(VALUE klass) { VALUE obj; CONF *conf; obj = TypedData_Wrap_Struct(klass, &ossl_config_type, 0); conf = NCONF_new(NULL); if (!conf) ossl_raise(eConfigError, "NCONF_new"); RTYPEDDATA_DATA(obj) = conf; return obj; } static void config_load_bio(CONF *conf, BIO *bio) { long eline = -1; if (!NCONF_load_bio(conf, bio, &eline)) { BIO_free(bio); if (eline <= 0) ossl_raise(eConfigError, "wrong config format"); else ossl_raise(eConfigError, "error in line %ld", eline); } BIO_free(bio); /* * Clear the error queue even if it is parsed successfully. * Particularly, when the .include directive refers to a non-existent file, * it is only reported in the error queue. */ ossl_clear_error(); } /* * call-seq: * Config.parse(string) -> OpenSSL::Config * * Parses a given _string_ as a blob that contains configuration for OpenSSL. */ static VALUE config_s_parse(VALUE klass, VALUE str) { VALUE obj = config_s_alloc(klass); CONF *conf = GetConfig(obj); BIO *bio; bio = ossl_obj2bio(&str); config_load_bio(conf, bio); /* Consumes BIO */ rb_obj_freeze(obj); return obj; } static VALUE config_get_sections(VALUE self); static VALUE config_get_section(VALUE self, VALUE section); /* * call-seq: * Config.parse_config(io) -> hash * * Parses the configuration data read from _io_ and returns the whole content * as a Hash. */ static VALUE config_s_parse_config(VALUE klass, VALUE io) { VALUE obj, sections, ret; long i; obj = config_s_parse(klass, io); sections = config_get_sections(obj); ret = rb_hash_new(); for (i = 0; i < RARRAY_LEN(sections); i++) { VALUE section = rb_ary_entry(sections, i); rb_hash_aset(ret, section, config_get_section(obj, section)); } return ret; } /* * call-seq: * Config.new(filename) -> OpenSSL::Config * * Creates an instance of OpenSSL::Config from the content of the file * specified by _filename_. * * This can be used in contexts like OpenSSL::X509::ExtensionFactory.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. */ static VALUE config_initialize(int argc, VALUE *argv, VALUE self) { CONF *conf = GetConfig(self); VALUE filename; /* 0-arguments call has no use-case, but is kept for compatibility */ rb_scan_args(argc, argv, "01", &filename); rb_check_frozen(self); if (!NIL_P(filename)) { BIO *bio = BIO_new_file(StringValueCStr(filename), "rb"); if (!bio) ossl_raise(eConfigError, "BIO_new_file"); config_load_bio(conf, bio); /* Consumes BIO */ } rb_obj_freeze(self); return self; } static VALUE config_initialize_copy(VALUE self, VALUE other) { CONF *conf = GetConfig(self); VALUE str; BIO *bio; str = rb_funcall(other, rb_intern("to_s"), 0); rb_check_frozen(self); bio = ossl_obj2bio(&str); config_load_bio(conf, bio); /* Consumes BIO */ rb_obj_freeze(self); return self; } /* * call-seq: * config.get_value(section, key) -> string * * Gets the value of _key_ from the given _section_. * * Given the following configurating file being loaded: * * config = OpenSSL::Config.load('foo.cnf') * #=> # * 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" */ static VALUE config_get_value(VALUE self, VALUE section, VALUE key) { CONF *conf = GetConfig(self); const char *str, *sectionp; StringValueCStr(section); StringValueCStr(key); /* For compatibility; NULL means "default". */ sectionp = RSTRING_LEN(section) ? RSTRING_PTR(section) : NULL; str = NCONF_get_string(conf, sectionp, RSTRING_PTR(key)); if (!str) { ossl_clear_error(); return Qnil; } return rb_str_new_cstr(str); } /* * call-seq: * config[section] -> hash * * Gets all key-value pairs in a specific _section_ from the current * configuration. * * Given the following configurating file being loaded: * * config = OpenSSL::Config.load('foo.cnf') * #=> # * puts config.to_s * #=> [ default ] * # foo=bar * * You can get a hash of the specific section like so: * * config['default'] * #=> {"foo"=>"bar"} * */ static VALUE config_get_section(VALUE self, VALUE section) { CONF *conf = GetConfig(self); STACK_OF(CONF_VALUE) *sk; int i, entries; VALUE hash; hash = rb_hash_new(); StringValueCStr(section); if (!(sk = NCONF_get_section(conf, RSTRING_PTR(section)))) { ossl_clear_error(); return hash; } entries = sk_CONF_VALUE_num(sk); for (i = 0; i < entries; i++) { CONF_VALUE *entry = sk_CONF_VALUE_value(sk, i); rb_hash_aset(hash, rb_str_new_cstr(entry->name), rb_str_new_cstr(entry->value)); } return hash; } static void get_conf_section_doall_arg(CONF_VALUE *cv, VALUE *aryp) { if (cv->name) return; rb_ary_push(*aryp, rb_str_new_cstr(cv->section)); } /* IMPLEMENT_LHASH_DOALL_ARG_CONST() requires >= OpenSSL 1.1.0 */ static IMPLEMENT_LHASH_DOALL_ARG_FN(get_conf_section, CONF_VALUE, VALUE) /* * call-seq: * config.sections -> array of string * * Get the names of all sections in the current configuration. */ static VALUE config_get_sections(VALUE self) { CONF *conf = GetConfig(self); VALUE ary; ary = rb_ary_new(); lh_doall_arg((_LHASH *)conf->data, LHASH_DOALL_ARG_FN(get_conf_section), &ary); return ary; } static void dump_conf_value_doall_arg(CONF_VALUE *cv, VALUE *strp) { VALUE str = *strp; STACK_OF(CONF_VALUE) *sk; int i, num; if (cv->name) return; sk = (STACK_OF(CONF_VALUE) *)cv->value; num = sk_CONF_VALUE_num(sk); rb_str_cat_cstr(str, "[ "); rb_str_cat_cstr(str, cv->section); rb_str_cat_cstr(str, " ]\n"); for (i = 0; i < num; i++){ CONF_VALUE *v = sk_CONF_VALUE_value(sk, i); rb_str_cat_cstr(str, v->name ? v->name : "None"); rb_str_cat_cstr(str, "="); rb_str_cat_cstr(str, v->value ? v->value : "None"); rb_str_cat_cstr(str, "\n"); } rb_str_cat_cstr(str, "\n"); } static IMPLEMENT_LHASH_DOALL_ARG_FN(dump_conf_value, CONF_VALUE, VALUE) /* * call-seq: * config.to_s -> string * * * Gets the parsable form of the current configuration. * * Given the following configuration file being loaded: * * config = OpenSSL::Config.load('baz.cnf') * #=> # * puts config.to_s * #=> [ default ] * # foo=bar * # baz=buz * * You can 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) * #=> # * puts new_config * #=> [ default ] * foo=bar * baz=buz */ static VALUE config_to_s(VALUE self) { CONF *conf = GetConfig(self); VALUE str; str = rb_str_new(NULL, 0); lh_doall_arg((_LHASH *)conf->data, LHASH_DOALL_ARG_FN(dump_conf_value), &str); return str; } static void each_conf_value_doall_arg(CONF_VALUE *cv, void *unused) { STACK_OF(CONF_VALUE) *sk; VALUE section; int i, num; if (cv->name) return; sk = (STACK_OF(CONF_VALUE) *)cv->value; num = sk_CONF_VALUE_num(sk); section = rb_str_new_cstr(cv->section); for (i = 0; i < num; i++){ CONF_VALUE *v = sk_CONF_VALUE_value(sk, i); VALUE name = v->name ? rb_str_new_cstr(v->name) : Qnil; VALUE value = v->value ? rb_str_new_cstr(v->value) : Qnil; rb_yield(rb_ary_new3(3, section, name, value)); } } static IMPLEMENT_LHASH_DOALL_ARG_FN(each_conf_value, CONF_VALUE, void) /* * call-seq: * config.each { |section, key, value| } * * Retrieves the section and its pairs for the current configuration. * * config.each do |section, key, value| * # ... * end */ static VALUE config_each(VALUE self) { CONF *conf = GetConfig(self); RETURN_ENUMERATOR(self, 0, 0); lh_doall_arg((_LHASH *)conf->data, LHASH_DOALL_ARG_FN(each_conf_value), NULL); return self; } /* * call-seq: * config.inspect -> string * * String representation of this configuration object, including the class * name and its sections. */ static VALUE config_inspect(VALUE self) { VALUE str, ary = config_get_sections(self); const char *cname = rb_class2name(rb_obj_class(self)); str = rb_str_new_cstr("#<"); rb_str_cat_cstr(str, cname); rb_str_cat_cstr(str, " sections="); rb_str_append(str, rb_inspect(ary)); rb_str_cat_cstr(str, ">"); return str; } void Init_ossl_config(void) { char *path; VALUE path_str; /* Document-class: 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 https://docs.openssl.org/master/man5/config/ */ cConfig = rb_define_class_under(mOSSL, "Config", rb_cObject); /* Document-class: OpenSSL::ConfigError * * General error for openssl library configuration files. Including formatting, * parsing errors, etc. */ eConfigError = rb_define_class_under(mOSSL, "ConfigError", eOSSLError); rb_include_module(cConfig, rb_mEnumerable); rb_define_singleton_method(cConfig, "parse", config_s_parse, 1); rb_define_singleton_method(cConfig, "parse_config", config_s_parse_config, 1); rb_define_alias(CLASS_OF(cConfig), "load", "new"); rb_define_alloc_func(cConfig, config_s_alloc); rb_define_method(cConfig, "initialize", config_initialize, -1); rb_define_method(cConfig, "initialize_copy", config_initialize_copy, 1); rb_define_method(cConfig, "get_value", config_get_value, 2); rb_define_method(cConfig, "[]", config_get_section, 1); rb_define_method(cConfig, "sections", config_get_sections, 0); rb_define_method(cConfig, "to_s", config_to_s, 0); rb_define_method(cConfig, "each", config_each, 0); rb_define_method(cConfig, "inspect", config_inspect, 0); /* Document-const: DEFAULT_CONFIG_FILE * * The default system configuration file for OpenSSL. */ path = CONF_get1_default_config_file(); path_str = rb_obj_freeze(ossl_buf2str(path, rb_long2int(strlen(path)))); rb_define_const(cConfig, "DEFAULT_CONFIG_FILE", path_str); } ================================================ FILE: ext/openssl/ossl_config.h ================================================ /* * 'OpenSSL for Ruby' project * Copyright (C) 2001-2002 Michal Rokos * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #ifndef OSSL_CONFIG_H #define OSSL_CONFIG_H CONF *GetConfig(VALUE obj); void Init_ossl_config(void); #endif /* OSSL_CONFIG_H */ ================================================ FILE: ext/openssl/ossl_digest.c ================================================ /* * 'OpenSSL for Ruby' project * Copyright (C) 2001-2002 Michal Rokos * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #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) /* * Classes */ static VALUE cDigest; static VALUE eDigestError; static ID id_md_holder; 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 | RUBY_TYPED_WB_PROTECTED, }; #ifdef OSSL_USE_PROVIDER static void ossl_evp_md_free(void *ptr) { // This is safe to call against const EVP_MD * returned by // EVP_get_digestbyname() EVP_MD_free(ptr); } static const rb_data_type_t ossl_evp_md_holder_type = { "OpenSSL/EVP_MD", { .dfree = ossl_evp_md_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; #endif /* * Public */ const EVP_MD * ossl_evp_md_fetch(VALUE obj, volatile VALUE *holder) { *holder = Qnil; if (rb_obj_is_kind_of(obj, cDigest)) { EVP_MD_CTX *ctx; GetDigest(obj, ctx); EVP_MD *md = (EVP_MD *)EVP_MD_CTX_get0_md(ctx); #ifdef OSSL_USE_PROVIDER *holder = TypedData_Wrap_Struct(0, &ossl_evp_md_holder_type, NULL); if (!EVP_MD_up_ref(md)) ossl_raise(eDigestError, "EVP_MD_up_ref"); RTYPEDDATA_DATA(*holder) = md; #endif return md; } const char *name = StringValueCStr(obj); EVP_MD *md = (EVP_MD *)EVP_get_digestbyname(name); if (!md) { ASN1_OBJECT *oid = OBJ_txt2obj(name, 0); md = (EVP_MD *)EVP_get_digestbyobj(oid); ASN1_OBJECT_free(oid); } #ifdef OSSL_USE_PROVIDER if (!md) { ossl_clear_error(); *holder = TypedData_Wrap_Struct(0, &ossl_evp_md_holder_type, NULL); md = EVP_MD_fetch(NULL, name, NULL); RTYPEDDATA_DATA(*holder) = md; } #endif if (!md) ossl_raise(eDigestError, "unsupported digest algorithm: %"PRIsVALUE, obj); return md; } VALUE ossl_digest_new(const EVP_MD *md) { VALUE ret; EVP_MD_CTX *ctx; // NOTE: This does not set id_md_holder because this function should // only be called from ossl_engine.c, which will not use any // reference-counted digests. 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); } static 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. A list of * supported algorithms can be obtained by calling OpenSSL::Digest.digests. * * 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 equivalent 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, md_holder; rb_scan_args(argc, argv, "11", &type, &data); md = ossl_evp_md_fetch(type, &md_holder); 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"); rb_ivar_set(self, id_md_holder, md_holder); if (!NIL_P(data)) return ossl_digest_update(self, data); return self; } /* :nodoc: */ 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"); } GetDigest(other, ctx2); if (!EVP_MD_CTX_copy(ctx1, ctx2)) { ossl_raise(eDigestError, NULL); } return self; } static void add_digest_name_to_ary(const OBJ_NAME *name, void *arg) { VALUE ary = (VALUE)arg; rb_ary_push(ary, rb_str_new2(name->name)); } /* * call-seq: * OpenSSL::Digest.digests -> array[string...] * * Returns the names of all available digests in an array. */ static VALUE ossl_s_digests(VALUE self) { VALUE ary; ary = rb_ary_new(); OBJ_NAME_do_all_sorted(OBJ_NAME_TYPE_MD_METH, add_digest_name_to_ary, (void*)ary); return ary; } /* * 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_get0_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.new('SHA256') * digest.update('First input') * digest << 'Second input' # equivalent to digest.update('Second input') * result = digest.digest * */ static 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(VALUE self) { EVP_MD_CTX *ctx; VALUE str; GetDigest(self, ctx); str = rb_str_new(NULL, EVP_MD_CTX_size(ctx)); 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 short name of this Digest algorithm which may differ slightly * from the original name provided. * * === Example * digest = OpenSSL::Digest.new('SHA512') * puts digest.name # => SHA512 * */ static VALUE ossl_digest_name(VALUE self) { EVP_MD_CTX *ctx; GetDigest(self, ctx); return rb_str_new_cstr(EVP_MD_name(EVP_MD_CTX_get0_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.new('SHA1') * 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.new('SHA1') * 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) { /* 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. * * You can get a list of all digest algorithms supported on your system by * running this command in your terminal: * * openssl list -digest-algorithms * * Among the OpenSSL 1.1.1 supported message digest algorithms are: * * SHA224, SHA256, SHA384, SHA512, SHA512-224 and SHA512-256 * * SHA3-224, SHA3-256, SHA3-384 and SHA3-512 * * BLAKE2s256 and BLAKE2b512 * * Each of these algorithms can be instantiated using the name: * * digest = OpenSSL::Digest.new('SHA256') * * "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.binread('document') * sha256 = OpenSSL::Digest.new('SHA256') * digest = sha256.digest(data) * * === Hashing several pieces of data at once * * data1 = File.binread('file1') * data2 = File.binread('file2') * data3 = File.binread('file3') * sha256 = OpenSSL::Digest.new('SHA256') * sha256 << data1 * sha256 << data2 * sha256 << data3 * digest = sha256.digest * * === Reuse a Digest instance * * data1 = File.binread('file1') * sha256 = OpenSSL::Digest.new('SHA256') * digest1 = sha256.digest(data1) * * data2 = File.binread('file2') * sha256.reset * digest2 = sha256.digest(data2) * */ /* * Digest::Class is defined by the digest library. rb_require() cannot be * used here because it bypasses RubyGems. */ rb_funcall(Qnil, rb_intern_const("require"), 1, rb_str_new_cstr("digest")); 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_module_function(cDigest, "digests", ossl_s_digests, 0); rb_define_method(cDigest, "initialize", ossl_digest_initialize, -1); rb_define_method(cDigest, "initialize_copy", ossl_digest_copy, 1); 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, 0); 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); id_md_holder = rb_intern_const("EVP_MD_holder"); } ================================================ FILE: ext/openssl/ossl_digest.h ================================================ /* * 'OpenSSL for Ruby' project * Copyright (C) 2001-2002 Michal Rokos * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #if !defined(_OSSL_DIGEST_H_) #define _OSSL_DIGEST_H_ /* * Gets EVP_MD from a String or an OpenSSL::Digest instance (discouraged, but * still supported for compatibility). A holder object is created if the EVP_MD * is a "fetched" algorithm. */ const EVP_MD *ossl_evp_md_fetch(VALUE obj, volatile VALUE *holder); /* * This is meant for OpenSSL::Engine#digest. EVP_MD must not be a fetched one. */ VALUE ossl_digest_new(const EVP_MD *); void Init_ossl_digest(void); #endif /* _OSSL_DIGEST_H_ */ ================================================ FILE: ext/openssl/ossl_engine.c ================================================ /* * 'OpenSSL for Ruby' project * Copyright (C) 2003 GOTOU Yuuzou * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #include "ossl.h" #ifdef OSSL_USE_ENGINE # include #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) /* * 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 */ static VALUE cEngine; /* Document-class: OpenSSL::Engine::EngineError * * This is the generic exception for OpenSSL::Engine related errors */ static VALUE eEngineError; /* * Private */ #define OSSL_ENGINE_LOAD_IF_MATCH(engine_name, x) \ do{\ if(!strcmp(#engine_name, RSTRING_PTR(name))){\ if (OPENSSL_init_crypto(OPENSSL_INIT_ENGINE_##x, NULL))\ return Qtrue;\ else\ ossl_raise(eEngineError, "OPENSSL_init_crypto"); \ }\ }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 | RUBY_TYPED_WB_PROTECTED, }; /* * call-seq: * OpenSSL::Engine.load(name = 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) { VALUE name; rb_scan_args(argc, argv, "01", &name); if(NIL_P(name)){ ENGINE_load_builtin_engines(); return Qtrue; } StringValueCStr(name); OSSL_ENGINE_LOAD_IF_MATCH(dynamic, DYNAMIC); OSSL_ENGINE_LOAD_IF_MATCH(padlock, PADLOCK); OSSL_ENGINE_LOAD_IF_MATCH(capi, CAPI); OSSL_ENGINE_LOAD_IF_MATCH(cryptodev, CRYPTODEV); OSSL_ENGINE_LOAD_IF_MATCH(openssl, OPENSSL); rb_warning("no such builtin loader for `%"PRIsVALUE"'", name); return Qnil; } /* * 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) { return Qnil; } /* * call-seq: * OpenSSL::Engine.engines -> [engine, ...] * * 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; } /* * call-seq: * OpenSSL::Engine.by_id(name) -> engine * * Fetches the engine as specified by the _id_ String. * * OpenSSL::Engine.by_id("openssl") * => # * * 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; } /* * call-seq: * engine.id -> string * * Gets the id for this engine. * * OpenSSL::Engine.load * OpenSSL::Engine.engines #=> [#, ...] * 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)); } /* * call-seq: * engine.name -> string * * Get the descriptive name for this engine. * * OpenSSL::Engine.load * OpenSSL::Engine.engines #=> [#, ...] * 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)); } /* * call-seq: * engine.finish -> nil * * 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; } /* * call-seq: * engine.cipher(name) -> OpenSSL::Cipher * * Returns a new instance of 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") * => # * e.cipher("RC4") * => # * */ 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); } /* * call-seq: * engine.digest(name) -> OpenSSL::Digest * * Returns a new instance of OpenSSL::Digest by _name_. * * Will raise an EngineError if the digest is unavailable. * * e = OpenSSL::Engine.by_id("openssl") * #=> # * e.digest("SHA1") * #=> # * 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); } /* * call-seq: * engine.load_private_key(id = nil, data = nil) -> OpenSSL::PKey * * Loads the given private key identified 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_wrap(pkey); OSSL_PKEY_SET_PRIVATE(obj); return obj; } /* * call-seq: * engine.load_public_key(id = nil, data = nil) -> OpenSSL::PKey * * Loads the given public key identified 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_wrap(pkey); } /* * 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 */ 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; } /* * call-seq: * engine.ctrl_cmd(command, value = nil) -> engine * * Sends 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"); } } /* * call-seq: * engine.cmds -> [["name", "description", "flags"], ...] * * 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; } /* * call-seq: * engine.inspect -> string * * Pretty prints 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) { 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); DefEngineConst(METHOD_CIPHERS); DefEngineConst(METHOD_DIGESTS); DefEngineConst(METHOD_ALL); DefEngineConst(METHOD_NONE); } #else void Init_ossl_engine(void) { } #endif ================================================ FILE: ext/openssl/ossl_engine.h ================================================ /* * 'OpenSSL for Ruby' project * Copyright (C) 2003 Michal Rokos * Copyright (C) 2003 GOTOU Yuuzou * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #if !defined(OSSL_ENGINE_H) #define OSSL_ENGINE_H void Init_ossl_engine(void); #endif /* OSSL_ENGINE_H */ ================================================ FILE: ext/openssl/ossl_hmac.c ================================================ /* * 'OpenSSL for Ruby' project * Copyright (C) 2001-2002 Michal Rokos * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #include "ossl.h" #define NewHMAC(klass) \ TypedData_Wrap_Struct((klass), &ossl_hmac_type, 0) #define GetHMAC(obj, ctx) do { \ TypedData_Get_Struct((obj), EVP_MD_CTX, &ossl_hmac_type, (ctx)); \ if (!(ctx)) { \ ossl_raise(rb_eRuntimeError, "HMAC wasn't initialized"); \ } \ } while (0) /* * Classes */ static VALUE cHMAC; static VALUE eHMACError; static ID id_md_holder; /* * Public */ /* * Private */ static void ossl_hmac_free(void *ctx) { EVP_MD_CTX_free(ctx); } static const rb_data_type_t ossl_hmac_type = { "OpenSSL/HMAC", { 0, ossl_hmac_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; static VALUE ossl_hmac_alloc(VALUE klass) { VALUE obj; EVP_MD_CTX *ctx; obj = NewHMAC(klass); ctx = EVP_MD_CTX_new(); if (!ctx) ossl_raise(eHMACError, "EVP_MD_CTX"); 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' * instance = OpenSSL::HMAC.new(key, 'SHA1') * #=> f42bb0eeb018ebbd4597ae7213711ec60760843f * instance.class * #=> OpenSSL::HMAC * * === A note about comparisons * * Two instances can be securely compared with #== in constant time: * * other_instance = OpenSSL::HMAC.new('key', 'SHA1') * #=> f42bb0eeb018ebbd4597ae7213711ec60760843f * instance == other_instance * #=> true * */ static VALUE ossl_hmac_initialize(VALUE self, VALUE key, VALUE digest) { EVP_MD_CTX *ctx; EVP_PKEY *pkey; const EVP_MD *md; VALUE md_holder; GetHMAC(self, ctx); StringValue(key); md = ossl_evp_md_fetch(digest, &md_holder); pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_HMAC, NULL, (unsigned char *)RSTRING_PTR(key), RSTRING_LENINT(key)); if (!pkey) ossl_raise(eHMACError, "EVP_PKEY_new_raw_private_key"); if (EVP_DigestSignInit(ctx, NULL, md, NULL, pkey) != 1) { EVP_PKEY_free(pkey); ossl_raise(eHMACError, "EVP_DigestSignInit"); } rb_ivar_set(self, id_md_holder, md_holder); /* Decrement reference counter; EVP_MD_CTX still keeps it */ EVP_PKEY_free(pkey); return self; } /* :nodoc: */ static VALUE ossl_hmac_copy(VALUE self, VALUE other) { EVP_MD_CTX *ctx1, *ctx2; rb_check_frozen(self); if (self == other) return self; GetHMAC(self, ctx1); GetHMAC(other, ctx2); if (EVP_MD_CTX_copy(ctx1, ctx2) != 1) ossl_raise(eHMACError, "EVP_MD_CTX_copy"); return self; } /* * call-seq: * hmac.update(string) -> self * * Returns _hmac_ 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) { EVP_MD_CTX *ctx; StringValue(data); GetHMAC(self, ctx); if (EVP_DigestSignUpdate(ctx, RSTRING_PTR(data), RSTRING_LEN(data)) != 1) ossl_raise(eHMACError, "EVP_DigestSignUpdate"); return self; } /* * call-seq: * hmac.digest -> string * * Returns the authentication code an instance represents as a binary string. * * === Example * instance = OpenSSL::HMAC.new('key', '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) { EVP_MD_CTX *ctx; size_t buf_len = EVP_MAX_MD_SIZE; VALUE ret; GetHMAC(self, ctx); ret = rb_str_new(NULL, EVP_MAX_MD_SIZE); if (EVP_DigestSignFinal(ctx, (unsigned char *)RSTRING_PTR(ret), &buf_len) != 1) ossl_raise(eHMACError, "EVP_DigestSignFinal"); rb_str_set_len(ret, (long)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) { EVP_MD_CTX *ctx; unsigned char buf[EVP_MAX_MD_SIZE]; size_t buf_len = EVP_MAX_MD_SIZE; VALUE ret; GetHMAC(self, ctx); if (EVP_DigestSignFinal(ctx, buf, &buf_len) != 1) ossl_raise(eHMACError, "EVP_DigestSignFinal"); ret = rb_str_new(NULL, buf_len * 2); ossl_bin2hex(buf, RSTRING_PTR(ret), buf_len); return ret; } /* * call-seq: * hmac.reset -> self * * Returns _hmac_ 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', 'SHA1') * #=> f42bb0eeb018ebbd4597ae7213711ec60760843f * * instance.update(data) * #=> de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9 * instance.reset * #=> f42bb0eeb018ebbd4597ae7213711ec60760843f * */ static VALUE ossl_hmac_reset(VALUE self) { EVP_MD_CTX *ctx; EVP_PKEY *pkey; GetHMAC(self, ctx); pkey = EVP_PKEY_CTX_get0_pkey(EVP_MD_CTX_get_pkey_ctx(ctx)); if (EVP_DigestSignInit(ctx, NULL, EVP_MD_CTX_get0_md(ctx), NULL, pkey) != 1) ossl_raise(eHMACError, "EVP_DigestSignInit"); return self; } /* * INIT */ void Init_ossl_hmac(void) { /* * 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.binread("file1") * data2 = File.binread("file2") * key = "key" * hmac = OpenSSL::HMAC.new(key, 'SHA256') * 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_method(cHMAC, "initialize", ossl_hmac_initialize, 2); rb_define_method(cHMAC, "initialize_copy", ossl_hmac_copy, 1); 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"); id_md_holder = rb_intern_const("EVP_MD_holder"); } ================================================ FILE: ext/openssl/ossl_hmac.h ================================================ /* * 'OpenSSL for Ruby' project * Copyright (C) 2001-2002 Michal Rokos * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #if !defined(_OSSL_HMAC_H_) #define _OSSL_HMAC_H_ void Init_ossl_hmac(void); #endif /* _OSSL_HMAC_H_ */ ================================================ FILE: ext/openssl/ossl_kdf.c ================================================ /* * Ruby/OpenSSL Project * Copyright (C) 2007, 2017 Ruby/OpenSSL Project Authors */ #include "ossl.h" #include static VALUE mKDF, eKDF; struct pbkdf2_hmac_args { char *pass; int passlen; unsigned char *salt; int saltlen; int iters; const EVP_MD *md; int len; unsigned char *out; }; static void * pbkdf2_hmac_nogvl(void *args_) { struct pbkdf2_hmac_args *args = (struct pbkdf2_hmac_args *)args_; int ret = PKCS5_PBKDF2_HMAC(args->pass, args->passlen, args->salt, args->saltlen, args->iters, args->md, args->len, args->out); return (void *)(uintptr_t)ret; } /* * call-seq: * KDF.pbkdf2_hmac(pass, salt:, iterations:, length:, hash:) -> aString * * PKCS #5 PBKDF2 (Password-Based Key Derivation Function 2) in combination * with HMAC. Takes _pass_, _salt_ and _iterations_, and then derives a key * of _length_ bytes. * * For more information about PBKDF2, see RFC 2898 Section 5.2 * (https://www.rfc-editor.org/rfc/rfc2898#section-5.2). * * === Parameters * pass :: The password. * salt :: The salt. Salts prevent attacks based on dictionaries of common * passwords and attacks based on rainbow tables. It is a public * value that can be safely stored along with the password (e.g. * if the derived value is used for password storage). * iterations :: The iteration count. This provides the ability to tune the * algorithm. It is better to use the highest count possible for * the maximum resistance to brute-force attacks. * length :: The desired length of the derived key in octets. * hash :: The hash algorithm used with HMAC for the PRF. May be a String * representing the algorithm name, or an instance of * OpenSSL::Digest. */ static VALUE kdf_pbkdf2_hmac(int argc, VALUE *argv, VALUE self) { VALUE pass, salt, opts, kwargs[4], str, md_holder, pass_tmp, salt_tmp; static ID kwargs_ids[4]; int passlen, saltlen, iters, len; const EVP_MD *md; if (!kwargs_ids[0]) { kwargs_ids[0] = rb_intern_const("salt"); kwargs_ids[1] = rb_intern_const("iterations"); kwargs_ids[2] = rb_intern_const("length"); kwargs_ids[3] = rb_intern_const("hash"); } rb_scan_args(argc, argv, "1:", &pass, &opts); rb_get_kwargs(opts, kwargs_ids, 4, 0, kwargs); StringValue(pass); salt = StringValue(kwargs[0]); iters = NUM2INT(kwargs[1]); len = NUM2INT(kwargs[2]); md = ossl_evp_md_fetch(kwargs[3], &md_holder); passlen = RSTRING_LENINT(pass); saltlen = RSTRING_LENINT(salt); str = rb_str_new(NULL, len); struct pbkdf2_hmac_args args = { .pass = ALLOCV(pass_tmp, passlen), .passlen = passlen, .salt = ALLOCV(salt_tmp, saltlen), .saltlen = saltlen, .iters = iters, .md = md, .len = len, .out = (unsigned char *)RSTRING_PTR(str), }; memcpy(args.pass, RSTRING_PTR(pass), passlen); memcpy(args.salt, RSTRING_PTR(salt), saltlen); if (!rb_thread_call_without_gvl(pbkdf2_hmac_nogvl, &args, NULL, NULL)) ossl_raise(eKDF, "PKCS5_PBKDF2_HMAC"); OPENSSL_cleanse(args.pass, passlen); ALLOCV_END(pass_tmp); ALLOCV_END(salt_tmp); return str; } #if defined(HAVE_EVP_PBE_SCRYPT) struct scrypt_args { char *pass; size_t passlen; unsigned char *salt; size_t saltlen; uint64_t N, r, p; size_t len; unsigned char *out; }; static void * scrypt_nogvl(void *args_) { struct scrypt_args *args = (struct scrypt_args *)args_; /* * OpenSSL uses 32MB by default (if zero is specified), which is too * small. Let's not limit memory consumption but just let malloc() fail * inside OpenSSL. The amount is controllable by other parameters. */ uint64_t maxmem = UINT64_MAX; int ret = EVP_PBE_scrypt(args->pass, args->passlen, args->salt, args->saltlen, args->N, args->r, args->p, maxmem, args->out, args->len); return (void *)(uintptr_t)ret; } /* * call-seq: * KDF.scrypt(pass, salt:, N:, r:, p:, length:) -> aString * * Derives a key from _pass_ using given parameters with the scrypt * password-based key derivation function. The result can be used for password * storage. * * scrypt is designed to be memory-hard and more secure against brute-force * attacks using custom hardwares than alternative KDFs such as PBKDF2 or * bcrypt. * * The keyword arguments _N_, _r_ and _p_ can be used to tune scrypt. RFC 7914 * (published on 2016-08, https://www.rfc-editor.org/rfc/rfc7914#section-2) states * that using values r=8 and p=1 appears to yield good results. * * See RFC 7914 (https://www.rfc-editor.org/rfc/rfc7914) for more information. * * === Parameters * pass :: Passphrase. * salt :: Salt. * N :: CPU/memory cost parameter. This must be a power of 2. * r :: Block size parameter. * p :: Parallelization parameter. * length :: Length in octets of the derived key. * * === Example * pass = "password" * salt = SecureRandom.random_bytes(16) * dk = OpenSSL::KDF.scrypt(pass, salt: salt, N: 2**14, r: 8, p: 1, length: 32) * p dk #=> "\xDA\xE4\xE2...\x7F\xA1\x01T" */ static VALUE kdf_scrypt(int argc, VALUE *argv, VALUE self) { VALUE pass, salt, opts, kwargs[5], str, pass_tmp, salt_tmp; static ID kwargs_ids[5]; size_t passlen, saltlen; long len; uint64_t N, r, p; if (!kwargs_ids[0]) { kwargs_ids[0] = rb_intern_const("salt"); kwargs_ids[1] = rb_intern_const("N"); kwargs_ids[2] = rb_intern_const("r"); kwargs_ids[3] = rb_intern_const("p"); kwargs_ids[4] = rb_intern_const("length"); } rb_scan_args(argc, argv, "1:", &pass, &opts); rb_get_kwargs(opts, kwargs_ids, 5, 0, kwargs); StringValue(pass); salt = StringValue(kwargs[0]); N = NUM2UINT64T(kwargs[1]); r = NUM2UINT64T(kwargs[2]); p = NUM2UINT64T(kwargs[3]); len = NUM2LONG(kwargs[4]); passlen = RSTRING_LEN(pass); saltlen = RSTRING_LEN(salt); str = rb_str_new(NULL, len); struct scrypt_args args = { .pass = ALLOCV(pass_tmp, passlen), .passlen = passlen, .salt = ALLOCV(salt_tmp, saltlen), .saltlen = saltlen, .N = N, .r = r, .p = p, .len = len, .out = (unsigned char *)RSTRING_PTR(str), }; memcpy(args.pass, RSTRING_PTR(pass), passlen); memcpy(args.salt, RSTRING_PTR(salt), saltlen); if (!rb_thread_call_without_gvl(scrypt_nogvl, &args, NULL, NULL)) ossl_raise(eKDF, "EVP_PBE_scrypt"); OPENSSL_cleanse(args.pass, passlen); ALLOCV_END(pass_tmp); ALLOCV_END(salt_tmp); return str; } #endif /* * call-seq: * KDF.hkdf(ikm, salt:, info:, length:, hash:) -> String * * HMAC-based Extract-and-Expand Key Derivation Function (HKDF) as specified in * {RFC 5869}[https://www.rfc-editor.org/rfc/rfc5869]. * * New in OpenSSL 1.1.0. * * === Parameters * _ikm_:: * The input keying material. * _salt_:: * The salt. * _info_:: * The context and application specific information. * _length_:: * The output length in octets. Must be <= 255 * HashLen, where * HashLen is the length of the hash function output in octets. * _hash_:: * The hash function. * * === Example * # The values from https://www.rfc-editor.org/rfc/rfc5869#appendix-A.1 * ikm = ["0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"].pack("H*") * salt = ["000102030405060708090a0b0c"].pack("H*") * info = ["f0f1f2f3f4f5f6f7f8f9"].pack("H*") * p OpenSSL::KDF.hkdf(ikm, salt: salt, info: info, length: 42, hash: "SHA256").unpack1("H*") * # => "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865" */ static VALUE kdf_hkdf(int argc, VALUE *argv, VALUE self) { VALUE ikm, salt, info, opts, kwargs[4], str, md_holder; static ID kwargs_ids[4]; int saltlen, ikmlen, infolen; size_t len; const EVP_MD *md; EVP_PKEY_CTX *pctx; if (!kwargs_ids[0]) { kwargs_ids[0] = rb_intern_const("salt"); kwargs_ids[1] = rb_intern_const("info"); kwargs_ids[2] = rb_intern_const("length"); kwargs_ids[3] = rb_intern_const("hash"); } rb_scan_args(argc, argv, "1:", &ikm, &opts); rb_get_kwargs(opts, kwargs_ids, 4, 0, kwargs); StringValue(ikm); ikmlen = RSTRING_LENINT(ikm); salt = StringValue(kwargs[0]); saltlen = RSTRING_LENINT(salt); info = StringValue(kwargs[1]); infolen = RSTRING_LENINT(info); len = (size_t)NUM2LONG(kwargs[2]); if (len > LONG_MAX) rb_raise(rb_eArgError, "length must be non-negative"); md = ossl_evp_md_fetch(kwargs[3], &md_holder); str = rb_str_new(NULL, (long)len); pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); if (!pctx) ossl_raise(eKDF, "EVP_PKEY_CTX_new_id"); if (EVP_PKEY_derive_init(pctx) <= 0) { EVP_PKEY_CTX_free(pctx); ossl_raise(eKDF, "EVP_PKEY_derive_init"); } if (EVP_PKEY_CTX_set_hkdf_md(pctx, md) <= 0) { EVP_PKEY_CTX_free(pctx); ossl_raise(eKDF, "EVP_PKEY_CTX_set_hkdf_md"); } if (EVP_PKEY_CTX_set1_hkdf_salt(pctx, (unsigned char *)RSTRING_PTR(salt), saltlen) <= 0) { EVP_PKEY_CTX_free(pctx); ossl_raise(eKDF, "EVP_PKEY_CTX_set_hkdf_salt"); } if (EVP_PKEY_CTX_set1_hkdf_key(pctx, (unsigned char *)RSTRING_PTR(ikm), ikmlen) <= 0) { EVP_PKEY_CTX_free(pctx); ossl_raise(eKDF, "EVP_PKEY_CTX_set_hkdf_key"); } if (EVP_PKEY_CTX_add1_hkdf_info(pctx, (unsigned char *)RSTRING_PTR(info), infolen) <= 0) { EVP_PKEY_CTX_free(pctx); ossl_raise(eKDF, "EVP_PKEY_CTX_set_hkdf_info"); } if (EVP_PKEY_derive(pctx, (unsigned char *)RSTRING_PTR(str), &len) <= 0) { EVP_PKEY_CTX_free(pctx); ossl_raise(eKDF, "EVP_PKEY_derive"); } rb_str_set_len(str, (long)len); EVP_PKEY_CTX_free(pctx); return str; } void Init_ossl_kdf(void) { /* * Document-module: OpenSSL::KDF * * Provides functionality of various KDFs (key derivation function). * * KDF is 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. * * Currently, OpenSSL::KDF provides implementations for the following KDF: * * * PKCS #5 PBKDF2 (Password-Based Key Derivation Function 2) in * combination with HMAC * * scrypt * * HKDF * * == Examples * === Generating a 128 bit key for a Cipher (e.g. AES) * pass = "secret" * salt = OpenSSL::Random.random_bytes(16) * iter = 20_000 * key_len = 16 * key = OpenSSL::KDF.pbkdf2_hmac(pass, salt: salt, iterations: iter, * length: key_len, hash: "sha1") * * === Storing Passwords * pass = "secret" * # store this with the generated value * salt = OpenSSL::Random.random_bytes(16) * iter = 20_000 * hash = OpenSSL::Digest.new('SHA256') * len = hash.digest_length * # the final value to be stored * value = OpenSSL::KDF.pbkdf2_hmac(pass, salt: salt, iterations: iter, * length: len, hash: hash) * * == 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 do this, use * +OpenSSL.fixed_length_secure_compare+. */ mKDF = rb_define_module_under(mOSSL, "KDF"); /* * Generic exception class raised if an error occurs in OpenSSL::KDF module. */ eKDF = rb_define_class_under(mKDF, "KDFError", eOSSLError); rb_define_module_function(mKDF, "pbkdf2_hmac", kdf_pbkdf2_hmac, -1); #if defined(HAVE_EVP_PBE_SCRYPT) rb_define_module_function(mKDF, "scrypt", kdf_scrypt, -1); #endif rb_define_module_function(mKDF, "hkdf", kdf_hkdf, -1); } ================================================ FILE: ext/openssl/ossl_kdf.h ================================================ #if !defined(OSSL_KDF_H) #define OSSL_KDF_H void Init_ossl_kdf(void); #endif ================================================ FILE: ext/openssl/ossl_ns_spki.c ================================================ /* * 'OpenSSL for Ruby' project * Copyright (C) 2001-2002 Michal Rokos * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #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 */ static VALUE mNetscape; static VALUE cSPKI; static 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 | RUBY_TYPED_WB_PROTECTED, }; 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(eSPKIError, "i2d_NETSCAPE_SPKI"); str = rb_str_new(0, len); p = (unsigned char *)RSTRING_PTR(str); if (i2d_NETSCAPE_SPKI(spki, &p) <= 0) ossl_raise(eSPKIError, "i2d_NETSCAPE_SPKI"); 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_wrap(pkey); } /* * 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; EVP_PKEY *pkey; GetSPKI(self, spki); pkey = GetPKeyPtr(key); ossl_pkey_check_public_key(pkey); if (!NETSCAPE_SPKI_set_pubkey(spki, pkey)) ossl_raise(eSPKIError, "NETSCAPE_SPKI_set_pubkey"); 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 (ASN1_STRING_length(spki->spkac->challenge) <= 0) { OSSL_Debug("Challenge.length <= 0?"); return rb_str_new(0, 0); } return asn1str_to_str(spki->spkac->challenge); } /* * 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; VALUE md_holder; pkey = GetPrivPKeyPtr(key); /* NO NEED TO DUP */ md = ossl_evp_md_fetch(digest, &md_holder); GetSPKI(self, spki); if (!NETSCAPE_SPKI_sign(spki, pkey, md)) ossl_raise(eSPKIError, "NETSCAPE_SPKI_sign"); 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; EVP_PKEY *pkey; GetSPKI(self, spki); pkey = GetPKeyPtr(key); ossl_pkey_check_public_key(pkey); switch (NETSCAPE_SPKI_verify(spki, pkey)) { case 0: ossl_clear_error(); return Qfalse; case 1: return Qtrue; default: ossl_raise(eSPKIError, "NETSCAPE_SPKI_verify"); } } /* 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 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.new('SHA256')) * #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}[https://www.rfc-editor.org/rfc/rfc2692] and {RFC * 2693}[https://www.rfc-editor.org/rfc/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) { 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); } ================================================ FILE: ext/openssl/ossl_ns_spki.h ================================================ /* * 'OpenSSL for Ruby' project * Copyright (C) 2001-2002 Michal Rokos * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #if !defined(_OSSL_NS_SPKI_H_) #define _OSSL_NS_SPKI_H_ void Init_ossl_ns_spki(void); #endif /* _OSSL_NS_SPKI_H_ */ ================================================ FILE: ext/openssl/ossl_ocsp.c ================================================ /* * 'OpenSSL for Ruby' project * Copyright (C) 2003 Michal Rokos * Copyright (C) 2003 GOTOU Yuuzou * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #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 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 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 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 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) static VALUE mOCSP; static VALUE eOCSPError; static VALUE cOCSPReq; static VALUE cOCSPRes; static VALUE cOCSPBasicRes; static VALUE cOCSPSingleRes; static 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 | RUBY_TYPED_WB_PROTECTED, }; 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 | RUBY_TYPED_WB_PROTECTED, }; 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 | RUBY_TYPED_WB_PROTECTED, }; 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 | RUBY_TYPED_WB_PROTECTED, }; 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 | RUBY_TYPED_WB_PROTECTED, }; /* * Public */ static VALUE ossl_ocspcid_new(const OCSP_CERTID *cid) { VALUE obj = NewOCSPCertId(cOCSPCertId); /* OpenSSL 1.1.1 takes a non-const pointer */ OCSP_CERTID *cid_new = OCSP_CERTID_dup((OCSP_CERTID *)cid); if (!cid_new) ossl_raise(eOCSPError, "OCSP_CERTID_dup"); SetOCSPCertId(obj, cid_new); return obj; } /* * OCSP::Request */ 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; } /* :nodoc: */ 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); GetOCSPReq(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 result.nonzero? may be necessary. A result of * 0 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); GetOCSPBasicRes(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; GetOCSPReq(self, req); int count = OCSP_request_onereq_count(req); if (count < 0) ossl_raise(eOCSPError, "OCSP_request_onereq_count"); if (count == 0) return Qnil; VALUE ary = rb_ary_new_capa(count); for (int i = 0; i < count; i++) { OCSP_ONEREQ *one = OCSP_request_onereq_get0(req, i); OCSP_CERTID *cid = OCSP_onereq_get0_id(one); rb_ary_push(ary, ossl_ocspcid_new(cid)); } 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_ is 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, md_holder; 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); md = NIL_P(digest) ? NULL : ossl_evp_md_fetch(digest, &md_holder); 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, "OCSP_request_sign"); 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. * * Note that +false+ is returned if the request does not have a signature. * Use #signed? to check whether the request is signed or not. */ 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; } /* * call-seq: * request.signed? -> true or false * * Returns +true+ if the request is signed, +false+ otherwise. Note that the * validity of the signature is *not* checked. Use #verify to verify that. */ static VALUE ossl_ocspreq_signed_p(VALUE self) { OCSP_REQUEST *req; GetOCSPReq(self, req); return OCSP_request_is_signed(req) ? Qtrue : Qfalse; } /* * 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; } /* :nodoc: */ 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); GetOCSPRes(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; } /* :nodoc: */ 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); GetOCSPBasicRes(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); GetOCSPReq(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 the 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); GetOCSPCertId(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::Extension. * * This should be superseded by BasicResponse#responses and #find_response that * return SingleResponse. */ static VALUE ossl_ocspbres_get_status(VALUE self) { OCSP_BASICRESP *bs; GetOCSPBasicRes(self, bs); VALUE ret = rb_ary_new(); int count = OCSP_resp_count(bs); for (int i = 0; i < count; i++) { OCSP_SINGLERESP *single = OCSP_resp_get0(bs, i); ASN1_TIME *revtime = NULL, *thisupd = NULL, *nextupd = NULL; int reason = -1; int status = OCSP_single_get0_status(single, &reason, &revtime, &thisupd, &nextupd); if (status < 0) ossl_raise(eOCSPError, "OCSP_single_get0_status"); VALUE ary = rb_ary_new(); rb_ary_push(ary, ossl_ocspcid_new(OCSP_SINGLERESP_get0_id(single))); 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); VALUE ext = rb_ary_new(); int ext_count = OCSP_SINGLERESP_get_ext_count(single); for (int j = 0; j < ext_count; j++) { const X509_EXTENSION *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(const 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_new_capa(count); for (i = 0; i < count; i++) { rb_ary_push(ret, ossl_ocspsres_new(OCSP_resp_get0(bs, i))); } 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_CERTID *id; int n; GetOCSPCertId(target, id); GetOCSPBasicRes(self, bs); if ((n = OCSP_resp_find(bs, id, -1)) == -1) return Qnil; return ossl_ocspsres_new(OCSP_resp_get0(bs, n)); } /* * 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, md_holder; 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); md = NIL_P(digest) ? NULL : ossl_evp_md_fetch(digest, &md_holder); 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, "OCSP_basic_sign"); 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); result = OCSP_basic_verify(bs, x509s, x509st, flg); 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(const OCSP_SINGLERESP *sres) { VALUE obj; OCSP_SINGLERESP *sres_new; obj = NewOCSPSingleRes(cOCSPSingleRes); /* OpenSSL 1.1.1 takes a non-const pointer */ sres_new = ASN1_item_dup(ASN1_ITEM_rptr(OCSP_SINGLERESP), (OCSP_SINGLERESP *)sres); if (!sres_new) ossl_raise(eOCSPError, "ASN1_item_dup"); SetOCSPSingleRes(obj, sres_new); 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; } /* :nodoc: */ 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); GetOCSPSingleRes(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_ seconds 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; GetOCSPSingleRes(self, sres); return ossl_ocspcid_new(OCSP_SINGLERESP_get0_id(sres)); } /* * 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"); if (!time) return Qnil; return asn1time_to_time(time); } /* * 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"); if (!time) return Qnil; 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"); if (!time) return Qnil; 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; 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++) { const X509_EXTENSION *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; } /* :nodoc: */ 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); GetOCSPCertId(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 * OpenSSL::OCSP::CertificateId.new(obj) -> certificate_id * * Creates a new OpenSSL::OCSP::CertificateId for the given _subject_ and * _issuer_ X509 certificates. The _digest_ is a digest algorithm that is used * to compute the hash values. This defaults to SHA-1. * * If only one argument is given, decodes it as DER representation of a * certificate ID or generates certificate ID from the object that responds to * the to_der method. */ 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; VALUE md_holder; x509s = GetX509CertPtr(subject); /* NO NEED TO DUP */ x509i = GetX509CertPtr(issuer); /* NO NEED TO DUP */ md = NIL_P(digest) ? NULL : ossl_evp_md_fetch(digest, &md_holder); 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); GetOCSPCertId(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); GetOCSPCertId(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, ASN1_STRING_length(name_hash) * 2); ossl_bin2hex(ASN1_STRING_get0_data(name_hash), RSTRING_PTR(ret), ASN1_STRING_length(name_hash)); 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, ASN1_STRING_length(key_hash) * 2); ossl_bin2hex(ASN1_STRING_get0_data(key_hash), RSTRING_PTR(ret), ASN1_STRING_length(key_hash)); 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; GetOCSPCertId(self, id); OCSP_id_get0_info(NULL, &oid, NULL, NULL, id); return ossl_asn1obj_to_string_long_name(oid); } /* * 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) { /* * 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.new('SHA1') * 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: * * ocsp_uris = subject.ocsp_uris * * require 'uri' * * ocsp_uri = URI ocsp_uris[0] * * 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_uri.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 certificate' * 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_method(cOCSPReq, "initialize_copy", ossl_ocspreq_initialize_copy, 1); 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, "signed?", ossl_ocspreq_signed_p, 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_method(cOCSPRes, "initialize_copy", ossl_ocspres_initialize_copy, 1); 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_method(cOCSPBasicRes, "initialize_copy", ossl_ocspbres_initialize_copy, 1); 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_method(cOCSPSingleRes, "initialize_copy", ossl_ocspsres_initialize_copy, 1); 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_method(cOCSPCertId, "initialize_copy", ossl_ocspcid_initialize_copy, 1); 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 ================================================ FILE: ext/openssl/ossl_ocsp.h ================================================ /* * 'OpenSSL for Ruby' project * Copyright (C) 2003 Michal Rokos * Copyright (C) 2003 GOTOU Yuuzou * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #if !defined(_OSSL_OCSP_H_) #define _OSSL_OCSP_H_ void Init_ossl_ocsp(void); #endif /* _OSSL_OCSP_H_ */ ================================================ FILE: ext/openssl/ossl_pkcs12.c ================================================ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #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 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 */ static VALUE cPKCS12; static 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 | RUBY_TYPED_WB_PROTECTED, }; 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; } /* :nodoc: */ static VALUE ossl_pkcs12_initialize_copy(VALUE self, VALUE other) { PKCS12 *p12, *p12_old, *p12_new; rb_check_frozen(self); GetPKCS12(self, p12_old); GetPKCS12(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); #if defined(OPENSSL_IS_AWSLC) if (ktype != 0) { ossl_raise(rb_eArgError, "Unknown key usage type %"PRIsVALUE, INT2NUM(ktype)); } #else if (ktype != 0 && ktype != KEY_SIG && ktype != KEY_EX) { ossl_raise(rb_eArgError, "Unknown key usage type %"PRIsVALUE, INT2NUM(ktype)); } #endif 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; } static VALUE ossl_pkey_wrap_i(VALUE arg) { return ossl_pkey_wrap((EVP_PKEY *)arg); } static VALUE ossl_x509_new_i(VALUE arg) { return ossl_x509_new((X509 *)arg); } static VALUE ossl_x509_sk2ary_i(VALUE arg) { return ossl_x509_sk2ary((STACK_OF(X509) *)arg); } /* * 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; if(!PKCS12_parse(pkcs, passphrase, &key, &x509, &x509s)) ossl_raise(ePKCS12Error, "PKCS12_parse"); if (key) { pkey = rb_protect(ossl_pkey_wrap_i, (VALUE)key, &st); if (st) goto err; } if (x509) { cert = rb_protect(ossl_x509_new_i, (VALUE)x509, &st); if (st) goto err; } if (x509s) { ca = rb_protect(ossl_x509_sk2ary_i, (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; } /* * call-seq: * pkcs12.set_mac(pass, salt = nil, iter = nil, md_type = nil) * * Sets MAC parameters and generates MAC over the PKCS #12 structure. * * This method uses HMAC and the PKCS #12 specific password-based KDF as * specified in the original PKCS #12. * * See also the man page PKCS12_set_mac(3). * * Added in version 3.3.0. */ static VALUE pkcs12_set_mac(int argc, VALUE *argv, VALUE self) { PKCS12 *p12; VALUE pass, salt, iter, md_name, md_holder = Qnil; int iter_i = 0; const EVP_MD *md_type = NULL; rb_scan_args(argc, argv, "13", &pass, &salt, &iter, &md_name); rb_check_frozen(self); GetPKCS12(self, p12); StringValue(pass); if (!NIL_P(salt)) StringValue(salt); if (!NIL_P(iter)) iter_i = NUM2INT(iter); if (!NIL_P(md_name)) md_type = ossl_evp_md_fetch(md_name, &md_holder); if (!PKCS12_set_mac(p12, RSTRING_PTR(pass), RSTRING_LENINT(pass), !NIL_P(salt) ? (unsigned char *)RSTRING_PTR(salt) : NULL, !NIL_P(salt) ? RSTRING_LENINT(salt) : 0, iter_i, md_type)) ossl_raise(ePKCS12Error, "PKCS12_set_mac"); return Qnil; } void Init_ossl_pkcs12(void) { #undef rb_intern /* * 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_method(cPKCS12, "initialize_copy", ossl_pkcs12_initialize_copy, 1); 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); rb_define_method(cPKCS12, "set_mac", pkcs12_set_mac, -1); #if !defined(OPENSSL_IS_AWSLC) /* MSIE specific PKCS12 key usage extensions */ rb_define_const(cPKCS12, "KEY_EX", INT2NUM(KEY_EX)); rb_define_const(cPKCS12, "KEY_SIG", INT2NUM(KEY_SIG)); #endif } ================================================ FILE: ext/openssl/ossl_pkcs12.h ================================================ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #if !defined(_OSSL_PKCS12_H_) #define _OSSL_PKCS12_H_ void Init_ossl_pkcs12(void); #endif /* _OSSL_PKCS12_H_ */ ================================================ FILE: ext/openssl/ossl_pkcs7.c ================================================ /* * 'OpenSSL for Ruby' project * Copyright (C) 2001-2002 Michal Rokos * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #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 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 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 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 */ static VALUE cPKCS7; static VALUE cPKCS7Signer; static VALUE cPKCS7Recipient; static VALUE ePKCS7Error; static ID id_md_holder, id_cipher_holder; 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 | RUBY_TYPED_WB_PROTECTED, }; VALUE ossl_pkcs7_new(PKCS7 *p7) { PKCS7 *new; VALUE obj = NewPKCS7(cPKCS7); new = PKCS7_dup(p7); if (!new) ossl_raise(ePKCS7Error, "PKCS7_dup"); SetPKCS7(obj, new); return obj; } 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 | RUBY_TYPED_WB_PROTECTED, }; 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 | RUBY_TYPED_WB_PROTECTED, }; /* * Public * (MADE PRIVATE UNTIL SOMEBODY WILL NEED THEM) */ static PKCS7_SIGNER_INFO * ossl_PKCS7_SIGNER_INFO_dup(PKCS7_SIGNER_INFO *si) { PKCS7_SIGNER_INFO *si_new = ASN1_dup((i2d_of_void *)i2d_PKCS7_SIGNER_INFO, (d2i_of_void *)d2i_PKCS7_SIGNER_INFO, si); if (si_new && si->pkey) { EVP_PKEY_up_ref(si->pkey); si_new->pkey = si->pkey; } return si_new; } static PKCS7_RECIP_INFO * ossl_PKCS7_RECIP_INFO_dup(PKCS7_RECIP_INFO *ri) { PKCS7_RECIP_INFO *ri_new = ASN1_dup((i2d_of_void *)i2d_PKCS7_RECIP_INFO, (d2i_of_void *)d2i_PKCS7_RECIP_INFO, ri); if (ri_new && ri->cert) { if (!X509_up_ref(ri->cert)) { PKCS7_RECIP_INFO_free(ri_new); return NULL; } ri_new->cert = ri->cert; } return ri_new; } static VALUE ossl_pkcs7si_new(PKCS7_SIGNER_INFO *p7si) { PKCS7_SIGNER_INFO *p7si_new; VALUE obj; obj = NewPKCS7si(cPKCS7Signer); p7si_new = ossl_PKCS7_SIGNER_INFO_dup(p7si); if (!p7si_new) ossl_raise(ePKCS7Error, "ASN1_dup"); SetPKCS7si(obj, p7si_new); return obj; } static VALUE ossl_pkcs7ri_new(PKCS7_RECIP_INFO *p7ri) { PKCS7_RECIP_INFO *p7ri_new; VALUE obj; obj = NewPKCS7ri(cPKCS7Recipient); p7ri_new = ossl_PKCS7_RECIP_INFO_dup(p7ri); if (!p7ri_new) ossl_raise(ePKCS7Error,"ASN1_dup"); SetPKCS7ri(obj, p7ri_new); return obj; } /* * 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, "Could not parse the PKCS7"); if (!pkcs7->d.ptr) { PKCS7_free(pkcs7); ossl_raise(ePKCS7Error, "No content in PKCS7"); } 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); GetPKCS7(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 = 0) => pkcs7 * * Creates a PKCS #7 enveloped-data structure. * * Before version 3.3.0, +cipher+ was optional and defaulted to * "RC2-40-CBC". * * See also the man page PKCS7_encrypt(3). */ static VALUE ossl_pkcs7_s_encrypt(int argc, VALUE *argv, VALUE klass) { VALUE certs, data, cipher, flags, cipher_holder; 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)) { rb_raise(rb_eArgError, "cipher must be specified. Before version 3.3, " \ "the default cipher was RC2-40-CBC."); } ciph = ossl_evp_cipher_fetch(cipher, &cipher_holder); 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, 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); rb_ivar_set(ret, id_cipher_holder, cipher_holder); 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, *p7_orig = RTYPEDDATA_DATA(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 = d2i_PKCS7_bio(in, NULL); if (!p7) { OSSL_BIO_reset(in); p7 = PEM_read_bio_PKCS7(in, NULL, NULL, NULL); } BIO_free(in); if (!p7) ossl_raise(ePKCS7Error, "Could not parse the PKCS7"); if (!p7->d.ptr) { PKCS7_free(p7); ossl_raise(ePKCS7Error, "No content in PKCS7"); } RTYPEDDATA_DATA(self) = p7; PKCS7_free(p7_orig); ossl_pkcs7_set_data(self, Qnil); ossl_pkcs7_set_err_string(self, Qnil); return self; } /* :nodoc: */ 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); GetPKCS7(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); if (!PKCS7_type_is_signed(p7)) return Qfalse; 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; const EVP_CIPHER *ciph; VALUE cipher_holder; GetPKCS7(self, pkcs7); ciph = ossl_evp_cipher_fetch(cipher, &cipher_holder); if (!PKCS7_set_cipher(pkcs7, ciph)) ossl_raise(ePKCS7Error, "PKCS7_set_cipher"); rb_ivar_set(self, id_cipher_holder, cipher_holder); return cipher; } static VALUE ossl_pkcs7_add_signer(VALUE self, VALUE signer) { PKCS7 *pkcs7; PKCS7_SIGNER_INFO *si, *si_new; GetPKCS7(self, pkcs7); GetPKCS7si(signer, si); si_new = ossl_PKCS7_SIGNER_INFO_dup(si); if (!si_new) ossl_raise(ePKCS7Error, "PKCS7_SIGNER_INFO_dup"); if (PKCS7_add_signer(pkcs7, si_new) != 1) { PKCS7_SIGNER_INFO_free(si_new); ossl_raise(ePKCS7Error, "PKCS7_add_signer"); } return self; } static VALUE ossl_pkcs7_get_signer(VALUE self) { PKCS7 *pkcs7; STACK_OF(PKCS7_SIGNER_INFO) *sk; int num, i; VALUE ary; GetPKCS7(self, pkcs7); if (!(sk = PKCS7_get_signer_info(pkcs7))) return rb_ary_new(); num = sk_PKCS7_SIGNER_INFO_num(sk); ary = rb_ary_new_capa(num); for (i=0; id.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(); num = sk_PKCS7_RECIP_INFO_num(sk); ary = rb_ary_new_capa(num); for (i=0; itype); 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); if (certs) { 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) { STACK_OF(X509) *certs = pkcs7_get_certs(self); if (!certs) return Qnil; return ossl_x509_sk2ary(certs); } 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); if (crls) { 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) { STACK_OF(X509_CRL) *crls = pkcs7_get_crls(self); if (!crls) return Qnil; return ossl_x509crl_sk2ary(crls); } 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; 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); data = ossl_membio2str(out); ossl_pkcs7_set_data(self, data); if (ok != 1) { const char *msg = ERR_reason_error_string(ERR_peek_error()); ossl_pkcs7_set_err_string(self, msg ? rb_str_new_cstr(msg) : Qnil); ossl_clear_error(); return Qfalse; } ossl_pkcs7_set_err_string(self, Qnil); return Qtrue; } 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, "12", &pkey, &cert, &flags); key = GetPrivPKeyPtr(pkey); /* NO NEED TO DUP */ x509 = NIL_P(cert) ? NULL : 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, ret; GetPKCS7(self, pkcs7); if (PKCS7_type_is_signed(pkcs7)) { if (!PKCS7_content_new(pkcs7, NID_pkcs7_data)) ossl_raise(ePKCS7Error, "PKCS7_content_new"); } in = ossl_obj2bio(&data); if (!(out = PKCS7_dataInit(pkcs7, NULL))) { BIO_free(in); ossl_raise(ePKCS7Error, "PKCS7_dataInit"); } for (;;) { if ((len = BIO_read(in, buf, sizeof(buf))) <= 0) break; if (BIO_write(out, buf, len) != len) { BIO_free_all(out); BIO_free(in); ossl_raise(ePKCS7Error, "BIO_write"); } } if (BIO_flush(out) <= 0) { BIO_free_all(out); BIO_free(in); ossl_raise(ePKCS7Error, "BIO_flush"); } ret = PKCS7_dataFinal(pkcs7, out); BIO_free_all(out); BIO_free(in); if (!ret) ossl_raise(ePKCS7Error, "PKCS7_dataFinal"); ossl_pkcs7_set_data(self, Qnil); 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_text(VALUE self) { PKCS7 *pkcs7; BIO *out; VALUE str; GetPKCS7(self, pkcs7); if(!(out = BIO_new(BIO_s_mem()))) ossl_raise(ePKCS7Error, NULL); if(!PKCS7_print_ctx(out, pkcs7, 0, NULL)) { BIO_free(out); ossl_raise(ePKCS7Error, NULL); } str = ossl_membio2str(out); 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; VALUE md_holder; pkey = GetPrivPKeyPtr(key); /* NO NEED TO DUP */ x509 = GetX509CertPtr(cert); /* NO NEED TO DUP */ md = ossl_evp_md_fetch(digest, &md_holder); GetPKCS7si(self, p7si); if (!(PKCS7_SIGNER_INFO_set(p7si, x509, pkey, md))) ossl_raise(ePKCS7Error, "PKCS7_SIGNER_INFO_set"); rb_ivar_set(self, id_md_holder, md_holder); 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; const 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) <= 0) { 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) { #undef rb_intern 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_method(cPKCS7, "initialize_copy", ossl_pkcs7_copy, 1); 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); rb_define_method(cPKCS7, "to_text", ossl_pkcs7_to_text, 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_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); id_md_holder = rb_intern_const("EVP_MD_holder"); id_cipher_holder = rb_intern_const("EVP_CIPHER_holder"); } ================================================ FILE: ext/openssl/ossl_pkcs7.h ================================================ /* * 'OpenSSL for Ruby' project * Copyright (C) 2001-2002 Michal Rokos * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #if !defined(_OSSL_PKCS7_H_) #define _OSSL_PKCS7_H_ VALUE ossl_pkcs7_new(PKCS7 *p7); void Init_ossl_pkcs7(void); #endif /* _OSSL_PKCS7_H_ */ ================================================ FILE: ext/openssl/ossl_pkey.c ================================================ /* * 'OpenSSL for Ruby' project * Copyright (C) 2001-2002 Michal Rokos * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #include "ossl.h" #ifdef OSSL_USE_ENGINE # include #endif /* * Classes */ VALUE mPKey; VALUE cPKey; VALUE ePKeyError; static ID id_private_q; 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 | RUBY_TYPED_WB_PROTECTED, }; static VALUE pkey_wrap0(VALUE arg) { EVP_PKEY *pkey = (EVP_PKEY *)arg; VALUE klass, obj; switch (EVP_PKEY_base_id(pkey)) { #if !defined(OPENSSL_NO_RSA) case EVP_PKEY_RSA: klass = cRSA; break; #endif #if !defined(OPENSSL_NO_DSA) case EVP_PKEY_DSA: klass = cDSA; break; #endif #if !defined(OPENSSL_NO_DH) case EVP_PKEY_DH: klass = cDH; break; #endif #if !defined(OPENSSL_NO_EC) case EVP_PKEY_EC: klass = cEC; break; #endif default: klass = cPKey; break; } obj = rb_obj_alloc(klass); RTYPEDDATA_DATA(obj) = pkey; return obj; } VALUE ossl_pkey_wrap(EVP_PKEY *pkey) { VALUE obj; int status; obj = rb_protect(pkey_wrap0, (VALUE)pkey, &status); if (status) { EVP_PKEY_free(pkey); rb_jump_tag(status); } return obj; } #if OSSL_OPENSSL_PREREQ(3, 0, 0) # include static EVP_PKEY * ossl_pkey_read(BIO *bio, const char *input_type, int selection, VALUE pass) { void *ppass = (void *)pass; OSSL_DECODER_CTX *dctx; EVP_PKEY *pkey = NULL; int pos = 0, pos2; dctx = OSSL_DECODER_CTX_new_for_pkey(&pkey, input_type, NULL, NULL, selection, NULL, NULL); if (!dctx) goto out; if (selection == EVP_PKEY_KEYPAIR && OSSL_DECODER_CTX_set_pem_password_cb(dctx, ossl_pem_passwd_cb, ppass) != 1) goto out; while (1) { if (OSSL_DECODER_from_bio(dctx, bio) == 1) goto out; if (BIO_eof(bio)) break; pos2 = BIO_tell(bio); if (pos2 < 0 || pos2 <= pos) break; ossl_clear_error(); pos = pos2; } out: OSSL_BIO_reset(bio); OSSL_DECODER_CTX_free(dctx); return pkey; } EVP_PKEY * ossl_pkey_read_generic(BIO *bio, VALUE pass) { EVP_PKEY *pkey = NULL; /* First check DER, then check PEM. */ const char *input_types[] = {"DER", "PEM"}; int input_type_num = (int)(sizeof(input_types) / sizeof(char *)); /* * Non-zero selections to try to decode. * * See EVP_PKEY_fromdata(3) - Selections to see all the selections. * * This is a workaround for the decoder failing to decode or returning * bogus keys with selection 0, if a key management provider is different * from a decoder provider. The workaround is to avoid using selection 0. * * Affected OpenSSL versions: >= 3.1.0, <= 3.1.2, or >= 3.0.0, <= 3.0.10 * Fixed OpenSSL versions: 3.2, next release of the 3.1.z and 3.0.z * * See https://github.com/openssl/openssl/pull/21519 for details. * * First check for private key formats (EVP_PKEY_KEYPAIR). This is to keep * compatibility with ruby/openssl < 3.0 which decoded the following as a * private key. * * $ openssl ecparam -name prime256v1 -genkey -outform PEM * -----BEGIN EC PARAMETERS----- * BggqhkjOPQMBBw== * -----END EC PARAMETERS----- * -----BEGIN EC PRIVATE KEY----- * MHcCAQEEIAG8ugBbA5MHkqnZ9ujQF93OyUfL9tk8sxqM5Wv5tKg5oAoGCCqGSM49 * AwEHoUQDQgAEVcjhJfkwqh5C7kGuhAf8XaAjVuG5ADwb5ayg/cJijCgs+GcXeedj * 86avKpGH84DXUlB23C/kPt+6fXYlitUmXQ== * -----END EC PRIVATE KEY----- * * While the first PEM block is a proper encoding of ECParameters, thus * OSSL_DECODER_from_bio() would pick it up, ruby/openssl used to return * the latter instead. Existing applications expect this behavior. * * Note that normally, the input is supposed to contain a single decodable * PEM block only, so this special handling should not create a new problem. * * Note that we need to create the OSSL_DECODER_CTX variable each time when * we use the different selection as a workaround. * See https://github.com/openssl/openssl/issues/20657 for details. */ int selections[] = { EVP_PKEY_KEYPAIR, EVP_PKEY_KEY_PARAMETERS, EVP_PKEY_PUBLIC_KEY }; int selection_num = (int)(sizeof(selections) / sizeof(int)); int i, j; for (i = 0; i < input_type_num; i++) { for (j = 0; j < selection_num; j++) { pkey = ossl_pkey_read(bio, input_types[i], selections[j], pass); if (pkey) { goto out; } } } out: return pkey; } #else EVP_PKEY * ossl_pkey_read_generic(BIO *bio, VALUE pass) { void *ppass = (void *)pass; EVP_PKEY *pkey; if ((pkey = d2i_PrivateKey_bio(bio, NULL))) goto out; OSSL_BIO_reset(bio); if ((pkey = d2i_PKCS8PrivateKey_bio(bio, NULL, ossl_pem_passwd_cb, ppass))) goto out; OSSL_BIO_reset(bio); if ((pkey = d2i_PUBKEY_bio(bio, NULL))) goto out; OSSL_BIO_reset(bio); /* PEM_read_bio_PrivateKey() also parses PKCS #8 formats */ if ((pkey = PEM_read_bio_PrivateKey(bio, NULL, ossl_pem_passwd_cb, ppass))) goto out; OSSL_BIO_reset(bio); if ((pkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL))) goto out; OSSL_BIO_reset(bio); if ((pkey = PEM_read_bio_Parameters(bio, NULL))) goto out; out: return pkey; } #endif /* * 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 _io_ 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); bio = ossl_obj2bio(&data); pkey = ossl_pkey_read_generic(bio, ossl_pem_passwd_value(pass)); BIO_free(bio); if (!pkey) ossl_raise(ePKeyError, "Could not parse PKey"); return ossl_pkey_wrap(pkey); } static VALUE pkey_ctx_apply_options_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, ctx_v)) { VALUE key = rb_ary_entry(i, 0), value = rb_ary_entry(i, 1); EVP_PKEY_CTX *ctx = (EVP_PKEY_CTX *)ctx_v; if (SYMBOL_P(key)) key = rb_sym2str(key); value = rb_String(value); if (EVP_PKEY_CTX_ctrl_str(ctx, StringValueCStr(key), StringValueCStr(value)) <= 0) ossl_raise(ePKeyError, "EVP_PKEY_CTX_ctrl_str(ctx, %+"PRIsVALUE", %+"PRIsVALUE")", key, value); return Qnil; } static VALUE pkey_ctx_apply_options0(VALUE args_v) { VALUE *args = (VALUE *)args_v; Check_Type(args[1], T_HASH); rb_block_call(args[1], rb_intern("each"), 0, NULL, pkey_ctx_apply_options_i, args[0]); return Qnil; } static void pkey_ctx_apply_options(EVP_PKEY_CTX *ctx, VALUE options, int *state) { VALUE args[2]; args[0] = (VALUE)ctx; args[1] = options; rb_protect(pkey_ctx_apply_options0, (VALUE)args, state); } struct pkey_blocking_generate_arg { EVP_PKEY_CTX *ctx; EVP_PKEY *pkey; int state; unsigned int yield: 1; unsigned int genparam: 1; unsigned int interrupted: 1; }; static VALUE pkey_gen_cb_yield(VALUE ctx_v) { EVP_PKEY_CTX *ctx = (void *)ctx_v; int i, info_num; VALUE *argv; info_num = EVP_PKEY_CTX_get_keygen_info(ctx, -1); argv = ALLOCA_N(VALUE, info_num); for (i = 0; i < info_num; i++) argv[i] = INT2NUM(EVP_PKEY_CTX_get_keygen_info(ctx, i)); return rb_yield_values2(info_num, argv); } static VALUE call_check_ints0(VALUE arg) { rb_thread_check_ints(); return Qnil; } static void * call_check_ints(void *arg) { int state; rb_protect(call_check_ints0, Qnil, &state); return (void *)(VALUE)state; } static int pkey_gen_cb(EVP_PKEY_CTX *ctx) { struct pkey_blocking_generate_arg *arg = EVP_PKEY_CTX_get_app_data(ctx); int state; if (arg->yield) { rb_protect(pkey_gen_cb_yield, (VALUE)ctx, &state); if (state) { arg->state = state; return 0; } } if (arg->interrupted) { arg->interrupted = 0; state = (int)(VALUE)rb_thread_call_with_gvl(call_check_ints, NULL); if (state) { arg->state = state; return 0; } } return 1; } static void pkey_blocking_gen_stop(void *ptr) { struct pkey_blocking_generate_arg *arg = ptr; arg->interrupted = 1; } static void * pkey_blocking_gen(void *ptr) { struct pkey_blocking_generate_arg *arg = ptr; if (arg->genparam && EVP_PKEY_paramgen(arg->ctx, &arg->pkey) <= 0) return NULL; if (!arg->genparam && EVP_PKEY_keygen(arg->ctx, &arg->pkey) <= 0) return NULL; return arg->pkey; } static VALUE pkey_generate(int argc, VALUE *argv, VALUE self, int genparam) { EVP_PKEY_CTX *ctx; VALUE alg, options; struct pkey_blocking_generate_arg gen_arg = { 0 }; int state; rb_scan_args(argc, argv, "11", &alg, &options); if (rb_obj_is_kind_of(alg, cPKey)) { EVP_PKEY *base_pkey; GetPKey(alg, base_pkey); ctx = EVP_PKEY_CTX_new(base_pkey, NULL/* engine */); if (!ctx) ossl_raise(ePKeyError, "EVP_PKEY_CTX_new"); } else { #if OSSL_OPENSSL_PREREQ(3, 0, 0) ctx = EVP_PKEY_CTX_new_from_name(NULL, StringValueCStr(alg), NULL); if (!ctx) ossl_raise(ePKeyError, "EVP_PKEY_CTX_new_from_name"); #else const EVP_PKEY_ASN1_METHOD *ameth; ENGINE *tmpeng; int pkey_id; StringValue(alg); ameth = EVP_PKEY_asn1_find_str(&tmpeng, RSTRING_PTR(alg), RSTRING_LENINT(alg)); if (!ameth) ossl_raise(ePKeyError, "algorithm %"PRIsVALUE" not found", alg); EVP_PKEY_asn1_get0_info(&pkey_id, NULL, NULL, NULL, NULL, ameth); #if !defined(OPENSSL_NO_ENGINE) if (tmpeng) ENGINE_finish(tmpeng); #endif ctx = EVP_PKEY_CTX_new_id(pkey_id, NULL/* engine */); if (!ctx) ossl_raise(ePKeyError, "EVP_PKEY_CTX_new_id"); #endif } if (genparam && EVP_PKEY_paramgen_init(ctx) <= 0) { EVP_PKEY_CTX_free(ctx); ossl_raise(ePKeyError, "EVP_PKEY_paramgen_init"); } if (!genparam && EVP_PKEY_keygen_init(ctx) <= 0) { EVP_PKEY_CTX_free(ctx); ossl_raise(ePKeyError, "EVP_PKEY_keygen_init"); } if (!NIL_P(options)) { pkey_ctx_apply_options(ctx, options, &state); if (state) { EVP_PKEY_CTX_free(ctx); rb_jump_tag(state); } } gen_arg.genparam = genparam; gen_arg.ctx = ctx; gen_arg.yield = rb_block_given_p(); EVP_PKEY_CTX_set_app_data(ctx, &gen_arg); EVP_PKEY_CTX_set_cb(ctx, pkey_gen_cb); if (gen_arg.yield) pkey_blocking_gen(&gen_arg); else rb_thread_call_without_gvl(pkey_blocking_gen, &gen_arg, pkey_blocking_gen_stop, &gen_arg); EVP_PKEY_CTX_free(ctx); if (!gen_arg.pkey) { if (gen_arg.state) { ossl_clear_error(); rb_jump_tag(gen_arg.state); } else { ossl_raise(ePKeyError, genparam ? "EVP_PKEY_paramgen" : "EVP_PKEY_keygen"); } } return ossl_pkey_wrap(gen_arg.pkey); } /* * call-seq: * OpenSSL::PKey.generate_parameters(algo_name [, options]) -> pkey * * Generates new parameters for the algorithm. _algo_name_ is a String that * represents the algorithm. The optional argument _options_ is a Hash that * specifies the options specific to the algorithm. The order of the options * can be important. * * A block can be passed optionally. The meaning of the arguments passed to * the block varies depending on the implementation of the algorithm. The block * may be called once or multiple times, or may not even be called. * * For the supported options, see the documentation for the 'openssl genpkey' * utility command. * * == Example * pkey = OpenSSL::PKey.generate_parameters("DSA", "dsa_paramgen_bits" => 2048) * p pkey.p.num_bits #=> 2048 */ static VALUE ossl_pkey_s_generate_parameters(int argc, VALUE *argv, VALUE self) { return pkey_generate(argc, argv, self, 1); } /* * call-seq: * OpenSSL::PKey.generate_key(algo_name [, options]) -> pkey * OpenSSL::PKey.generate_key(pkey [, options]) -> pkey * * Generates a new key (pair). * * If a String is given as the first argument, it generates a new random key * for the algorithm specified by the name just as ::generate_parameters does. * If an OpenSSL::PKey::PKey is given instead, it generates a new random key * for the same algorithm as the key, using the parameters the key contains. * * See ::generate_parameters for the details of _options_ and the given block. * * == Example * pkey_params = OpenSSL::PKey.generate_parameters("DSA", "dsa_paramgen_bits" => 2048) * pkey_params.priv_key #=> nil * pkey = OpenSSL::PKey.generate_key(pkey_params) * pkey.priv_key #=> # 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; } #ifdef HAVE_EVP_PKEY_DUP /* :nodoc: */ static VALUE ossl_pkey_initialize_copy(VALUE self, VALUE other) { EVP_PKEY *pkey, *pkey_other; TypedData_Get_Struct(self, EVP_PKEY, &ossl_evp_pkey_type, pkey); TypedData_Get_Struct(other, EVP_PKEY, &ossl_evp_pkey_type, pkey_other); if (pkey) rb_raise(rb_eTypeError, "pkey already initialized"); if (pkey_other) { pkey = EVP_PKEY_dup(pkey_other); if (!pkey) ossl_raise(ePKeyError, "EVP_PKEY_dup"); RTYPEDDATA_DATA(self) = pkey; } return self; } #endif #ifndef OSSL_USE_PROVIDER static int lookup_pkey_type(VALUE type) { const EVP_PKEY_ASN1_METHOD *ameth; int pkey_id; StringValue(type); /* * XXX: EVP_PKEY_asn1_find_str() looks up a PEM type string. Should we use * OBJ_txt2nid() instead (and then somehow check if the NID is an acceptable * EVP_PKEY type)? * It is probably fine, though, since it can handle all algorithms that * support raw keys in 1.1.1: { X25519, X448, ED25519, ED448, HMAC }. */ ameth = EVP_PKEY_asn1_find_str(NULL, RSTRING_PTR(type), RSTRING_LENINT(type)); if (!ameth) ossl_raise(ePKeyError, "algorithm %"PRIsVALUE" not found", type); EVP_PKEY_asn1_get0_info(&pkey_id, NULL, NULL, NULL, NULL, ameth); return pkey_id; } #endif /* * call-seq: * OpenSSL::PKey.new_raw_private_key(algo, string) -> PKey * * See the OpenSSL documentation for EVP_PKEY_new_raw_private_key() */ static VALUE ossl_pkey_new_raw_private_key(VALUE self, VALUE type, VALUE key) { EVP_PKEY *pkey; size_t keylen; StringValue(key); keylen = RSTRING_LEN(key); #ifdef OSSL_USE_PROVIDER pkey = EVP_PKEY_new_raw_private_key_ex(NULL, StringValueCStr(type), NULL, (unsigned char *)RSTRING_PTR(key), keylen); if (!pkey) ossl_raise(ePKeyError, "EVP_PKEY_new_raw_private_key_ex"); #else int pkey_id = lookup_pkey_type(type); pkey = EVP_PKEY_new_raw_private_key(pkey_id, NULL, (unsigned char *)RSTRING_PTR(key), keylen); if (!pkey) ossl_raise(ePKeyError, "EVP_PKEY_new_raw_private_key"); #endif return ossl_pkey_wrap(pkey); } /* * call-seq: * OpenSSL::PKey.new_raw_public_key(algo, string) -> PKey * * See the OpenSSL documentation for EVP_PKEY_new_raw_public_key() */ static VALUE ossl_pkey_new_raw_public_key(VALUE self, VALUE type, VALUE key) { EVP_PKEY *pkey; size_t keylen; StringValue(key); keylen = RSTRING_LEN(key); #ifdef OSSL_USE_PROVIDER pkey = EVP_PKEY_new_raw_public_key_ex(NULL, StringValueCStr(type), NULL, (unsigned char *)RSTRING_PTR(key), keylen); if (!pkey) ossl_raise(ePKeyError, "EVP_PKEY_new_raw_public_key_ex"); #else int pkey_id = lookup_pkey_type(type); pkey = EVP_PKEY_new_raw_public_key(pkey_id, NULL, (unsigned char *)RSTRING_PTR(key), keylen); if (!pkey) ossl_raise(ePKeyError, "EVP_PKEY_new_raw_public_key"); #endif return ossl_pkey_wrap(pkey); } /* * call-seq: * pkey.oid -> string * * Returns the short name of the OID associated with _pkey_. */ static VALUE ossl_pkey_oid(VALUE self) { EVP_PKEY *pkey; int nid; GetPKey(self, pkey); nid = EVP_PKEY_id(pkey); #ifdef OSSL_USE_PROVIDER if (nid == EVP_PKEY_KEYMGMT) ossl_raise(ePKeyError, "EVP_PKEY_id"); #endif return rb_str_new_cstr(OBJ_nid2sn(nid)); } /* * call-seq: * pkey.inspect -> string * * Returns a string describing the PKey object. */ static VALUE ossl_pkey_inspect(VALUE self) { EVP_PKEY *pkey; GetPKey(self, pkey); VALUE str = rb_sprintf("#<%"PRIsVALUE":%p", rb_obj_class(self), (void *)self); int nid = EVP_PKEY_id(pkey); #ifdef OSSL_USE_PROVIDER if (nid != EVP_PKEY_KEYMGMT) #endif rb_str_catf(str, " oid=%s", OBJ_nid2sn(nid)); #ifdef OSSL_USE_PROVIDER rb_str_catf(str, " type_name=%s", EVP_PKEY_get0_type_name(pkey)); const OSSL_PROVIDER *prov = EVP_PKEY_get0_provider(pkey); if (prov) rb_str_catf(str, " provider=%s", OSSL_PROVIDER_get0_name(prov)); #endif rb_str_catf(str, ">"); return str; } /* * call-seq: * pkey.to_text -> string * * Dumps key parameters, public key, and private key components contained in * the key into a human-readable text. * * This is intended for debugging purpose. * * See also the man page EVP_PKEY_print_private(3). */ static VALUE ossl_pkey_to_text(VALUE self) { EVP_PKEY *pkey; BIO *bio; GetPKey(self, pkey); if (!(bio = BIO_new(BIO_s_mem()))) ossl_raise(ePKeyError, "BIO_new"); if (EVP_PKEY_print_private(bio, pkey, 0, NULL) == 1) goto out; OSSL_BIO_reset(bio); if (EVP_PKEY_print_public(bio, pkey, 0, NULL) == 1) goto out; OSSL_BIO_reset(bio); if (EVP_PKEY_print_params(bio, pkey, 0, NULL) == 1) goto out; BIO_free(bio); ossl_raise(ePKeyError, "EVP_PKEY_print_params"); out: return ossl_membio2str(bio); } VALUE ossl_pkey_export_traditional(int argc, VALUE *argv, VALUE self, int to_der) { EVP_PKEY *pkey; VALUE cipher, pass, cipher_holder; const EVP_CIPHER *enc = NULL; BIO *bio; GetPKey(self, pkey); rb_scan_args(argc, argv, "02", &cipher, &pass); if (!NIL_P(cipher)) { enc = ossl_evp_cipher_fetch(cipher, &cipher_holder); pass = ossl_pem_passwd_value(pass); } bio = BIO_new(BIO_s_mem()); if (!bio) ossl_raise(ePKeyError, "BIO_new"); if (to_der) { if (!i2d_PrivateKey_bio(bio, pkey)) { BIO_free(bio); ossl_raise(ePKeyError, "i2d_PrivateKey_bio"); } } else { if (!PEM_write_bio_PrivateKey_traditional(bio, pkey, enc, NULL, 0, ossl_pem_passwd_cb, (void *)pass)) { BIO_free(bio); ossl_raise(ePKeyError, "PEM_write_bio_PrivateKey_traditional"); } } return ossl_membio2str(bio); } static VALUE do_pkcs8_export(int argc, VALUE *argv, VALUE self, int to_der) { EVP_PKEY *pkey; VALUE cipher, pass, cipher_holder; const EVP_CIPHER *enc = NULL; BIO *bio; GetPKey(self, pkey); rb_scan_args(argc, argv, "02", &cipher, &pass); if (argc > 0) { /* * TODO: EncryptedPrivateKeyInfo actually has more options. * Should they be exposed? */ enc = ossl_evp_cipher_fetch(cipher, &cipher_holder); pass = ossl_pem_passwd_value(pass); } bio = BIO_new(BIO_s_mem()); if (!bio) ossl_raise(ePKeyError, "BIO_new"); if (to_der) { if (!i2d_PKCS8PrivateKey_bio(bio, pkey, enc, NULL, 0, ossl_pem_passwd_cb, (void *)pass)) { BIO_free(bio); ossl_raise(ePKeyError, "i2d_PKCS8PrivateKey_bio"); } } else { if (!PEM_write_bio_PKCS8PrivateKey(bio, pkey, enc, NULL, 0, ossl_pem_passwd_cb, (void *)pass)) { BIO_free(bio); ossl_raise(ePKeyError, "PEM_write_bio_PKCS8PrivateKey"); } } return ossl_membio2str(bio); } /* * call-seq: * pkey.private_to_der -> string * pkey.private_to_der(cipher, password) -> string * * Serializes the private key to DER-encoded PKCS #8 format. If called without * arguments, unencrypted PKCS #8 PrivateKeyInfo format is used. If called with * a cipher name and a password, PKCS #8 EncryptedPrivateKeyInfo format with * PBES2 encryption scheme is used. */ static VALUE ossl_pkey_private_to_der(int argc, VALUE *argv, VALUE self) { return do_pkcs8_export(argc, argv, self, 1); } /* * call-seq: * pkey.private_to_pem -> string * pkey.private_to_pem(cipher, password) -> string * * Serializes the private key to PEM-encoded PKCS #8 format. See #private_to_der * for more details. * * An unencrypted PEM-encoded key will look like: * * -----BEGIN PRIVATE KEY----- * [...] * -----END PRIVATE KEY----- * * An encrypted PEM-encoded key will look like: * * -----BEGIN ENCRYPTED PRIVATE KEY----- * [...] * -----END ENCRYPTED PRIVATE KEY----- */ static VALUE ossl_pkey_private_to_pem(int argc, VALUE *argv, VALUE self) { return do_pkcs8_export(argc, argv, self, 0); } /* * call-seq: * pkey.raw_private_key => string * * See the OpenSSL documentation for EVP_PKEY_get_raw_private_key() */ static VALUE ossl_pkey_raw_private_key(VALUE self) { EVP_PKEY *pkey; VALUE str; size_t len; GetPKey(self, pkey); if (EVP_PKEY_get_raw_private_key(pkey, NULL, &len) != 1) ossl_raise(ePKeyError, "EVP_PKEY_get_raw_private_key"); str = rb_str_new(NULL, len); if (EVP_PKEY_get_raw_private_key(pkey, (unsigned char *)RSTRING_PTR(str), &len) != 1) ossl_raise(ePKeyError, "EVP_PKEY_get_raw_private_key"); rb_str_set_len(str, len); return str; } VALUE ossl_pkey_export_spki(VALUE self, int to_der) { EVP_PKEY *pkey; BIO *bio; GetPKey(self, pkey); ossl_pkey_check_public_key(pkey); bio = BIO_new(BIO_s_mem()); if (!bio) ossl_raise(ePKeyError, "BIO_new"); if (to_der) { if (!i2d_PUBKEY_bio(bio, pkey)) { BIO_free(bio); ossl_raise(ePKeyError, "i2d_PUBKEY_bio"); } } else { if (!PEM_write_bio_PUBKEY(bio, pkey)) { BIO_free(bio); ossl_raise(ePKeyError, "PEM_write_bio_PUBKEY"); } } return ossl_membio2str(bio); } /* * call-seq: * pkey.public_to_der -> string * * Serializes the public key to DER-encoded X.509 SubjectPublicKeyInfo format. */ static VALUE ossl_pkey_public_to_der(VALUE self) { return ossl_pkey_export_spki(self, 1); } /* * call-seq: * pkey.public_to_pem -> string * * Serializes the public key to PEM-encoded X.509 SubjectPublicKeyInfo format. * * A PEM-encoded key will look like: * * -----BEGIN PUBLIC KEY----- * [...] * -----END PUBLIC KEY----- */ static VALUE ossl_pkey_public_to_pem(VALUE self) { return ossl_pkey_export_spki(self, 0); } /* * call-seq: * pkey.raw_public_key => string * * See the OpenSSL documentation for EVP_PKEY_get_raw_public_key() */ static VALUE ossl_pkey_raw_public_key(VALUE self) { EVP_PKEY *pkey; VALUE str; size_t len; GetPKey(self, pkey); if (EVP_PKEY_get_raw_public_key(pkey, NULL, &len) != 1) ossl_raise(ePKeyError, "EVP_PKEY_get_raw_public_key"); str = rb_str_new(NULL, len); if (EVP_PKEY_get_raw_public_key(pkey, (unsigned char *)RSTRING_PTR(str), &len) != 1) ossl_raise(ePKeyError, "EVP_PKEY_get_raw_public_key"); rb_str_set_len(str, len); return str; } /* * call-seq: * pkey.compare?(another_pkey) -> true | false * * Used primarily to check if an OpenSSL::X509::Certificate#public_key compares to its private key. * * == Example * x509 = OpenSSL::X509::Certificate.new(pem_encoded_certificate) * rsa_key = OpenSSL::PKey::RSA.new(pem_encoded_private_key) * * rsa_key.compare?(x509.public_key) => true | false */ static VALUE ossl_pkey_compare(VALUE self, VALUE other) { int ret; EVP_PKEY *selfPKey; EVP_PKEY *otherPKey; GetPKey(self, selfPKey); GetPKey(other, otherPKey); /* Explicitly check the key type given EVP_PKEY_ASN1_METHOD(3) * docs param_cmp could return any negative number. */ if (EVP_PKEY_id(selfPKey) != EVP_PKEY_id(otherPKey)) ossl_raise(rb_eTypeError, "cannot match different PKey types"); ret = EVP_PKEY_eq(selfPKey, otherPKey); if (ret == 0) return Qfalse; else if (ret == 1) return Qtrue; else ossl_raise(ePKeyError, "EVP_PKEY_eq"); } /* * call-seq: * pkey.sign(digest, data [, options]) -> string * * Hashes and signs the +data+ using a message digest algorithm +digest+ and * a private key +pkey+. * * See #verify for the verification operation. * * See also the man page EVP_DigestSign(3). * * +digest+:: * A String that represents the message digest algorithm name, or +nil+ * if the PKey type requires no digest algorithm. * For backwards compatibility, this can be an instance of OpenSSL::Digest. * Its state will not affect the signature. * +data+:: * A String. The data to be hashed and signed. * +options+:: * A Hash that contains algorithm specific control operations to \OpenSSL. * See OpenSSL's man page EVP_PKEY_CTX_ctrl_str(3) for details. * +options+ parameter was added in version 3.0. * * Example: * data = "Sign me!" * pkey = OpenSSL::PKey.generate_key("RSA", rsa_keygen_bits: 2048) * signopts = { rsa_padding_mode: "pss" } * signature = pkey.sign("SHA256", data, signopts) * * # Creates a copy of the RSA key pkey, but without the private components * pub_key = pkey.public_key * puts pub_key.verify("SHA256", signature, data, signopts) # => true */ static VALUE ossl_pkey_sign(int argc, VALUE *argv, VALUE self) { EVP_PKEY *pkey; VALUE digest, data, options, sig, md_holder; const EVP_MD *md = NULL; EVP_MD_CTX *ctx; EVP_PKEY_CTX *pctx; size_t siglen; int state; pkey = GetPrivPKeyPtr(self); rb_scan_args(argc, argv, "21", &digest, &data, &options); if (!NIL_P(digest)) md = ossl_evp_md_fetch(digest, &md_holder); StringValue(data); ctx = EVP_MD_CTX_new(); if (!ctx) ossl_raise(ePKeyError, "EVP_MD_CTX_new"); if (EVP_DigestSignInit(ctx, &pctx, md, /* engine */NULL, pkey) < 1) { EVP_MD_CTX_free(ctx); ossl_raise(ePKeyError, "EVP_DigestSignInit"); } if (!NIL_P(options)) { pkey_ctx_apply_options(pctx, options, &state); if (state) { EVP_MD_CTX_free(ctx); rb_jump_tag(state); } } if (EVP_DigestSign(ctx, NULL, &siglen, (unsigned char *)RSTRING_PTR(data), RSTRING_LEN(data)) < 1) { EVP_MD_CTX_free(ctx); ossl_raise(ePKeyError, "EVP_DigestSign"); } if (siglen > LONG_MAX) { EVP_MD_CTX_free(ctx); rb_raise(ePKeyError, "signature would be too large"); } sig = ossl_str_new(NULL, (long)siglen, &state); if (state) { EVP_MD_CTX_free(ctx); rb_jump_tag(state); } if (EVP_DigestSign(ctx, (unsigned char *)RSTRING_PTR(sig), &siglen, (unsigned char *)RSTRING_PTR(data), RSTRING_LEN(data)) < 1) { EVP_MD_CTX_free(ctx); ossl_raise(ePKeyError, "EVP_DigestSign"); } EVP_MD_CTX_free(ctx); rb_str_set_len(sig, siglen); return sig; } /* * call-seq: * pkey.verify(digest, signature, data [, options]) -> true or false * * Verifies the +signature+ for the +data+ using a message digest algorithm * +digest+ and a public key +pkey+. * * Returns +true+ if the signature is successfully verified, +false+ otherwise. * The caller must check the return value. * * See #sign for the signing operation and an example. * * See also the man page EVP_DigestVerify(3). * * +digest+:: * See #sign. * +signature+:: * A String containing the signature to be verified. * +data+:: * See #sign. * +options+:: * See #sign. +options+ parameter was added in version 3.0. */ static VALUE ossl_pkey_verify(int argc, VALUE *argv, VALUE self) { EVP_PKEY *pkey; VALUE digest, sig, data, options, md_holder; const EVP_MD *md = NULL; EVP_MD_CTX *ctx; EVP_PKEY_CTX *pctx; int state, ret; GetPKey(self, pkey); rb_scan_args(argc, argv, "31", &digest, &sig, &data, &options); ossl_pkey_check_public_key(pkey); if (!NIL_P(digest)) md = ossl_evp_md_fetch(digest, &md_holder); StringValue(sig); StringValue(data); ctx = EVP_MD_CTX_new(); if (!ctx) ossl_raise(ePKeyError, "EVP_MD_CTX_new"); if (EVP_DigestVerifyInit(ctx, &pctx, md, /* engine */NULL, pkey) < 1) { EVP_MD_CTX_free(ctx); ossl_raise(ePKeyError, "EVP_DigestVerifyInit"); } if (!NIL_P(options)) { pkey_ctx_apply_options(pctx, options, &state); if (state) { EVP_MD_CTX_free(ctx); rb_jump_tag(state); } } ret = EVP_DigestVerify(ctx, (unsigned char *)RSTRING_PTR(sig), RSTRING_LEN(sig), (unsigned char *)RSTRING_PTR(data), RSTRING_LEN(data)); EVP_MD_CTX_free(ctx); if (ret < 0) ossl_raise(ePKeyError, "EVP_DigestVerify"); if (ret) return Qtrue; else { ossl_clear_error(); return Qfalse; } } /* * call-seq: * pkey.sign_raw(digest, data [, options]) -> string * * Signs +data+ using a private key +pkey+. Unlike #sign, +data+ will not be * hashed by +digest+ automatically. * * See #verify_raw for the verification operation. * * Added in version 3.0. See also the man page EVP_PKEY_sign(3). * * +digest+:: * A String that represents the message digest algorithm name, or +nil+ * if the PKey type requires no digest algorithm. * Although this method will not hash +data+ with it, this parameter may still * be required depending on the signature algorithm. * +data+:: * A String. The data to be signed. * +options+:: * A Hash that contains algorithm specific control operations to \OpenSSL. * See OpenSSL's man page EVP_PKEY_CTX_ctrl_str(3) for details. * * Example: * data = "Sign me!" * hash = OpenSSL::Digest.digest("SHA256", data) * pkey = OpenSSL::PKey.generate_key("RSA", rsa_keygen_bits: 2048) * signopts = { rsa_padding_mode: "pss" } * signature = pkey.sign_raw("SHA256", hash, signopts) * * # Creates a copy of the RSA key pkey, but without the private components * pub_key = pkey.public_key * puts pub_key.verify_raw("SHA256", signature, hash, signopts) # => true */ static VALUE ossl_pkey_sign_raw(int argc, VALUE *argv, VALUE self) { EVP_PKEY *pkey; VALUE digest, data, options, sig, md_holder; const EVP_MD *md = NULL; EVP_PKEY_CTX *ctx; size_t outlen; int state; GetPKey(self, pkey); rb_scan_args(argc, argv, "21", &digest, &data, &options); if (!NIL_P(digest)) md = ossl_evp_md_fetch(digest, &md_holder); StringValue(data); ctx = EVP_PKEY_CTX_new(pkey, /* engine */NULL); if (!ctx) ossl_raise(ePKeyError, "EVP_PKEY_CTX_new"); if (EVP_PKEY_sign_init(ctx) <= 0) { EVP_PKEY_CTX_free(ctx); ossl_raise(ePKeyError, "EVP_PKEY_sign_init"); } if (md && EVP_PKEY_CTX_set_signature_md(ctx, md) <= 0) { EVP_PKEY_CTX_free(ctx); ossl_raise(ePKeyError, "EVP_PKEY_CTX_set_signature_md"); } if (!NIL_P(options)) { pkey_ctx_apply_options(ctx, options, &state); if (state) { EVP_PKEY_CTX_free(ctx); rb_jump_tag(state); } } if (EVP_PKEY_sign(ctx, NULL, &outlen, (unsigned char *)RSTRING_PTR(data), RSTRING_LEN(data)) <= 0) { EVP_PKEY_CTX_free(ctx); ossl_raise(ePKeyError, "EVP_PKEY_sign"); } if (outlen > LONG_MAX) { EVP_PKEY_CTX_free(ctx); rb_raise(ePKeyError, "signature would be too large"); } sig = ossl_str_new(NULL, (long)outlen, &state); if (state) { EVP_PKEY_CTX_free(ctx); rb_jump_tag(state); } if (EVP_PKEY_sign(ctx, (unsigned char *)RSTRING_PTR(sig), &outlen, (unsigned char *)RSTRING_PTR(data), RSTRING_LEN(data)) <= 0) { EVP_PKEY_CTX_free(ctx); ossl_raise(ePKeyError, "EVP_PKEY_sign"); } EVP_PKEY_CTX_free(ctx); rb_str_set_len(sig, outlen); return sig; } /* * call-seq: * pkey.verify_raw(digest, signature, data [, options]) -> true or false * * Verifies the +signature+ for the +data+ using a public key +pkey+. Unlike * #verify, this method will not hash +data+ with +digest+ automatically. * * Returns +true+ if the signature is successfully verified, +false+ otherwise. * The caller must check the return value. * * See #sign_raw for the signing operation and an example code. * * Added in version 3.0. See also the man page EVP_PKEY_verify(3). * * +signature+:: * A String containing the signature to be verified. */ static VALUE ossl_pkey_verify_raw(int argc, VALUE *argv, VALUE self) { EVP_PKEY *pkey; VALUE digest, sig, data, options, md_holder; const EVP_MD *md = NULL; EVP_PKEY_CTX *ctx; int state, ret; GetPKey(self, pkey); rb_scan_args(argc, argv, "31", &digest, &sig, &data, &options); ossl_pkey_check_public_key(pkey); if (!NIL_P(digest)) md = ossl_evp_md_fetch(digest, &md_holder); StringValue(sig); StringValue(data); ctx = EVP_PKEY_CTX_new(pkey, /* engine */NULL); if (!ctx) ossl_raise(ePKeyError, "EVP_PKEY_CTX_new"); if (EVP_PKEY_verify_init(ctx) <= 0) { EVP_PKEY_CTX_free(ctx); ossl_raise(ePKeyError, "EVP_PKEY_verify_init"); } if (md && EVP_PKEY_CTX_set_signature_md(ctx, md) <= 0) { EVP_PKEY_CTX_free(ctx); ossl_raise(ePKeyError, "EVP_PKEY_CTX_set_signature_md"); } if (!NIL_P(options)) { pkey_ctx_apply_options(ctx, options, &state); if (state) { EVP_PKEY_CTX_free(ctx); rb_jump_tag(state); } } ret = EVP_PKEY_verify(ctx, (unsigned char *)RSTRING_PTR(sig), RSTRING_LEN(sig), (unsigned char *)RSTRING_PTR(data), RSTRING_LEN(data)); EVP_PKEY_CTX_free(ctx); if (ret < 0) ossl_raise(ePKeyError, "EVP_PKEY_verify"); if (ret) return Qtrue; else { ossl_clear_error(); return Qfalse; } } /* * call-seq: * pkey.verify_recover(digest, signature [, options]) -> string * * Recovers the signed data from +signature+ using a public key +pkey+. Not all * signature algorithms support this operation. * * Added in version 3.0. See also the man page EVP_PKEY_verify_recover(3). * * +signature+:: * A String containing the signature to be verified. */ static VALUE ossl_pkey_verify_recover(int argc, VALUE *argv, VALUE self) { EVP_PKEY *pkey; VALUE digest, sig, options, out, md_holder; const EVP_MD *md = NULL; EVP_PKEY_CTX *ctx; int state; size_t outlen; GetPKey(self, pkey); rb_scan_args(argc, argv, "21", &digest, &sig, &options); ossl_pkey_check_public_key(pkey); if (!NIL_P(digest)) md = ossl_evp_md_fetch(digest, &md_holder); StringValue(sig); ctx = EVP_PKEY_CTX_new(pkey, /* engine */NULL); if (!ctx) ossl_raise(ePKeyError, "EVP_PKEY_CTX_new"); if (EVP_PKEY_verify_recover_init(ctx) <= 0) { EVP_PKEY_CTX_free(ctx); ossl_raise(ePKeyError, "EVP_PKEY_verify_recover_init"); } if (md && EVP_PKEY_CTX_set_signature_md(ctx, md) <= 0) { EVP_PKEY_CTX_free(ctx); ossl_raise(ePKeyError, "EVP_PKEY_CTX_set_signature_md"); } if (!NIL_P(options)) { pkey_ctx_apply_options(ctx, options, &state); if (state) { EVP_PKEY_CTX_free(ctx); rb_jump_tag(state); } } if (EVP_PKEY_verify_recover(ctx, NULL, &outlen, (unsigned char *)RSTRING_PTR(sig), RSTRING_LEN(sig)) <= 0) { EVP_PKEY_CTX_free(ctx); ossl_raise(ePKeyError, "EVP_PKEY_verify_recover"); } out = ossl_str_new(NULL, (long)outlen, &state); if (state) { EVP_PKEY_CTX_free(ctx); rb_jump_tag(state); } if (EVP_PKEY_verify_recover(ctx, (unsigned char *)RSTRING_PTR(out), &outlen, (unsigned char *)RSTRING_PTR(sig), RSTRING_LEN(sig)) <= 0) { EVP_PKEY_CTX_free(ctx); ossl_raise(ePKeyError, "EVP_PKEY_verify_recover"); } EVP_PKEY_CTX_free(ctx); rb_str_set_len(out, outlen); return out; } /* * call-seq: * pkey.derive(peer_pkey) -> string * * Derives a shared secret from _pkey_ and _peer_pkey_. _pkey_ must contain * the private components, _peer_pkey_ must contain the public components. */ static VALUE ossl_pkey_derive(int argc, VALUE *argv, VALUE self) { EVP_PKEY *pkey, *peer_pkey; EVP_PKEY_CTX *ctx; VALUE peer_pkey_obj, str; size_t keylen; int state; GetPKey(self, pkey); rb_scan_args(argc, argv, "1", &peer_pkey_obj); GetPKey(peer_pkey_obj, peer_pkey); ctx = EVP_PKEY_CTX_new(pkey, /* engine */NULL); if (!ctx) ossl_raise(ePKeyError, "EVP_PKEY_CTX_new"); if (EVP_PKEY_derive_init(ctx) <= 0) { EVP_PKEY_CTX_free(ctx); ossl_raise(ePKeyError, "EVP_PKEY_derive_init"); } if (EVP_PKEY_derive_set_peer(ctx, peer_pkey) <= 0) { EVP_PKEY_CTX_free(ctx); ossl_raise(ePKeyError, "EVP_PKEY_derive_set_peer"); } if (EVP_PKEY_derive(ctx, NULL, &keylen) <= 0) { EVP_PKEY_CTX_free(ctx); ossl_raise(ePKeyError, "EVP_PKEY_derive"); } if (keylen > LONG_MAX) { EVP_PKEY_CTX_free(ctx); rb_raise(ePKeyError, "derived key would be too large"); } str = ossl_str_new(NULL, (long)keylen, &state); if (state) { EVP_PKEY_CTX_free(ctx); rb_jump_tag(state); } if (EVP_PKEY_derive(ctx, (unsigned char *)RSTRING_PTR(str), &keylen) <= 0) { EVP_PKEY_CTX_free(ctx); ossl_raise(ePKeyError, "EVP_PKEY_derive"); } EVP_PKEY_CTX_free(ctx); rb_str_set_len(str, keylen); return str; } /* * call-seq: * pkey.encrypt(data [, options]) -> string * * Performs a public key encryption operation using +pkey+. * * See #decrypt for the reverse operation. * * Added in version 3.0. See also the man page EVP_PKEY_encrypt(3). * * +data+:: * A String to be encrypted. * +options+:: * A Hash that contains algorithm specific control operations to \OpenSSL. * See OpenSSL's man page EVP_PKEY_CTX_ctrl_str(3) for details. * * Example: * pkey = OpenSSL::PKey.generate_key("RSA", rsa_keygen_bits: 2048) * data = "secret data" * encrypted = pkey.encrypt(data, rsa_padding_mode: "oaep") * decrypted = pkey.decrypt(data, rsa_padding_mode: "oaep") * p decrypted #=> "secret data" */ static VALUE ossl_pkey_encrypt(int argc, VALUE *argv, VALUE self) { EVP_PKEY *pkey; EVP_PKEY_CTX *ctx; VALUE data, options, str; size_t outlen; int state; GetPKey(self, pkey); rb_scan_args(argc, argv, "11", &data, &options); StringValue(data); ctx = EVP_PKEY_CTX_new(pkey, /* engine */NULL); if (!ctx) ossl_raise(ePKeyError, "EVP_PKEY_CTX_new"); if (EVP_PKEY_encrypt_init(ctx) <= 0) { EVP_PKEY_CTX_free(ctx); ossl_raise(ePKeyError, "EVP_PKEY_encrypt_init"); } if (!NIL_P(options)) { pkey_ctx_apply_options(ctx, options, &state); if (state) { EVP_PKEY_CTX_free(ctx); rb_jump_tag(state); } } if (EVP_PKEY_encrypt(ctx, NULL, &outlen, (unsigned char *)RSTRING_PTR(data), RSTRING_LEN(data)) <= 0) { EVP_PKEY_CTX_free(ctx); ossl_raise(ePKeyError, "EVP_PKEY_encrypt"); } if (outlen > LONG_MAX) { EVP_PKEY_CTX_free(ctx); rb_raise(ePKeyError, "encrypted data would be too large"); } str = ossl_str_new(NULL, (long)outlen, &state); if (state) { EVP_PKEY_CTX_free(ctx); rb_jump_tag(state); } if (EVP_PKEY_encrypt(ctx, (unsigned char *)RSTRING_PTR(str), &outlen, (unsigned char *)RSTRING_PTR(data), RSTRING_LEN(data)) <= 0) { EVP_PKEY_CTX_free(ctx); ossl_raise(ePKeyError, "EVP_PKEY_encrypt"); } EVP_PKEY_CTX_free(ctx); rb_str_set_len(str, outlen); return str; } /* * call-seq: * pkey.decrypt(data [, options]) -> string * * Performs a public key decryption operation using +pkey+. * * See #encrypt for a description of the parameters and an example. * * Added in version 3.0. See also the man page EVP_PKEY_decrypt(3). */ static VALUE ossl_pkey_decrypt(int argc, VALUE *argv, VALUE self) { EVP_PKEY *pkey; EVP_PKEY_CTX *ctx; VALUE data, options, str; size_t outlen; int state; GetPKey(self, pkey); rb_scan_args(argc, argv, "11", &data, &options); StringValue(data); ctx = EVP_PKEY_CTX_new(pkey, /* engine */NULL); if (!ctx) ossl_raise(ePKeyError, "EVP_PKEY_CTX_new"); if (EVP_PKEY_decrypt_init(ctx) <= 0) { EVP_PKEY_CTX_free(ctx); ossl_raise(ePKeyError, "EVP_PKEY_decrypt_init"); } if (!NIL_P(options)) { pkey_ctx_apply_options(ctx, options, &state); if (state) { EVP_PKEY_CTX_free(ctx); rb_jump_tag(state); } } if (EVP_PKEY_decrypt(ctx, NULL, &outlen, (unsigned char *)RSTRING_PTR(data), RSTRING_LEN(data)) <= 0) { EVP_PKEY_CTX_free(ctx); ossl_raise(ePKeyError, "EVP_PKEY_decrypt"); } if (outlen > LONG_MAX) { EVP_PKEY_CTX_free(ctx); rb_raise(ePKeyError, "decrypted data would be too large"); } str = ossl_str_new(NULL, (long)outlen, &state); if (state) { EVP_PKEY_CTX_free(ctx); rb_jump_tag(state); } if (EVP_PKEY_decrypt(ctx, (unsigned char *)RSTRING_PTR(str), &outlen, (unsigned char *)RSTRING_PTR(data), RSTRING_LEN(data)) <= 0) { EVP_PKEY_CTX_free(ctx); ossl_raise(ePKeyError, "EVP_PKEY_decrypt"); } EVP_PKEY_CTX_free(ctx); rb_str_set_len(str, outlen); return str; } /* * INIT */ void Init_ossl_pkey(void) { #undef rb_intern /* 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. * * Before version 4.0.0, OpenSSL::PKey::PKeyError had the following * subclasses. These subclasses have been removed and the constants are * now defined as aliases of OpenSSL::PKey::PKeyError. * * * OpenSSL::PKey::DHError * * OpenSSL::PKey::DSAError * * OpenSSL::PKey::ECError * * OpenSSL::PKey::RSAError */ 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_module_function(mPKey, "generate_parameters", ossl_pkey_s_generate_parameters, -1); rb_define_module_function(mPKey, "generate_key", ossl_pkey_s_generate_key, -1); rb_define_module_function(mPKey, "new_raw_private_key", ossl_pkey_new_raw_private_key, 2); rb_define_module_function(mPKey, "new_raw_public_key", ossl_pkey_new_raw_public_key, 2); rb_define_alloc_func(cPKey, ossl_pkey_alloc); rb_define_method(cPKey, "initialize", ossl_pkey_initialize, 0); #ifdef HAVE_EVP_PKEY_DUP rb_define_method(cPKey, "initialize_copy", ossl_pkey_initialize_copy, 1); #else rb_undef_method(cPKey, "initialize_copy"); #endif rb_define_method(cPKey, "oid", ossl_pkey_oid, 0); rb_define_method(cPKey, "inspect", ossl_pkey_inspect, 0); rb_define_method(cPKey, "to_text", ossl_pkey_to_text, 0); rb_define_method(cPKey, "private_to_der", ossl_pkey_private_to_der, -1); rb_define_method(cPKey, "private_to_pem", ossl_pkey_private_to_pem, -1); rb_define_method(cPKey, "public_to_der", ossl_pkey_public_to_der, 0); rb_define_method(cPKey, "public_to_pem", ossl_pkey_public_to_pem, 0); rb_define_method(cPKey, "raw_private_key", ossl_pkey_raw_private_key, 0); rb_define_method(cPKey, "raw_public_key", ossl_pkey_raw_public_key, 0); rb_define_method(cPKey, "compare?", ossl_pkey_compare, 1); rb_define_method(cPKey, "sign", ossl_pkey_sign, -1); rb_define_method(cPKey, "verify", ossl_pkey_verify, -1); rb_define_method(cPKey, "sign_raw", ossl_pkey_sign_raw, -1); rb_define_method(cPKey, "verify_raw", ossl_pkey_verify_raw, -1); rb_define_method(cPKey, "verify_recover", ossl_pkey_verify_recover, -1); rb_define_method(cPKey, "derive", ossl_pkey_derive, -1); rb_define_method(cPKey, "encrypt", ossl_pkey_encrypt, -1); rb_define_method(cPKey, "decrypt", ossl_pkey_decrypt, -1); id_private_q = rb_intern("private?"); /* * INIT rsa, dsa, dh, ec */ Init_ossl_rsa(); Init_ossl_dsa(); Init_ossl_dh(); Init_ossl_ec(); } ================================================ FILE: ext/openssl/ossl_pkey.h ================================================ /* * 'OpenSSL for Ruby' project * Copyright (C) 2001 Michal Rokos * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #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; /* For ENGINE */ #define OSSL_PKEY_SET_PRIVATE(obj) rb_ivar_set((obj), rb_intern("private"), Qtrue) #define OSSL_PKEY_IS_PRIVATE(obj) (rb_attr_get((obj), rb_intern("private")) == Qtrue) #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) /* Takes ownership of the EVP_PKEY */ VALUE ossl_pkey_wrap(EVP_PKEY *); void ossl_pkey_check_public_key(const EVP_PKEY *); EVP_PKEY *ossl_pkey_read_generic(BIO *, VALUE); EVP_PKEY *GetPKeyPtr(VALUE); EVP_PKEY *DupPKeyPtr(VALUE); EVP_PKEY *GetPrivPKeyPtr(VALUE); /* * Serializes _self_ in X.509 SubjectPublicKeyInfo format and returns the * resulting String. Sub-classes use this when overriding #to_der. */ VALUE ossl_pkey_export_spki(VALUE self, int to_der); /* * Serializes the private key _self_ in the traditional private key format * and returns the resulting String. Sub-classes use this when overriding * #to_der. */ VALUE ossl_pkey_export_traditional(int argc, VALUE *argv, VALUE self, int to_der); void Init_ossl_pkey(void); /* * RSA */ extern VALUE cRSA; void Init_ossl_rsa(void); /* * DSA */ extern VALUE cDSA; void Init_ossl_dsa(void); /* * DH */ extern VALUE cDH; void Init_ossl_dh(void); /* * EC */ extern VALUE cEC; 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) \ { \ const _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)) #ifndef OSSL_HAVE_IMMUTABLE_PKEY #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(ePKeyError, "BN_dup"); \ } \ \ 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(ePKeyError, "BN_dup"); \ } \ \ if (!_type##_set0_##_group(obj, bn1, bn2)) { \ BN_clear_free(bn1); \ BN_clear_free(bn2); \ ossl_raise(ePKeyError, #_type"_set0_"#_group); \ } \ return self; \ } #else #define OSSL_PKEY_BN_DEF_SETTER3(_keytype, _type, _group, a1, a2, a3) \ static VALUE ossl_##_keytype##_set_##_group(VALUE self, VALUE v1, VALUE v2, VALUE v3) \ { \ rb_raise(ePKeyError, \ #_keytype"#set_"#_group"= is incompatible with OpenSSL 3.0"); \ } #define OSSL_PKEY_BN_DEF_SETTER2(_keytype, _type, _group, a1, a2) \ static VALUE ossl_##_keytype##_set_##_group(VALUE self, VALUE v1, VALUE v2) \ { \ rb_raise(ePKeyError, \ #_keytype"#set_"#_group"= is incompatible with OpenSSL 3.0"); \ } #endif #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) #endif /* OSSL_PKEY_H */ ================================================ FILE: ext/openssl/ossl_pkey_dh.c ================================================ /* * 'OpenSSL for Ruby' project * Copyright (C) 2001-2002 Michal Rokos * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #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); \ if ((dh) == NULL) \ ossl_raise(ePKeyError, "failed to get DH from EVP_PKEY"); \ } while (0) /* * Classes */ VALUE cDH; /* * Private */ /* * call-seq: * DH.new -> dh * DH.new(string) -> dh * DH.new(size [, generator]) -> dh * * Creates a new instance of OpenSSL::PKey::DH. * * If called without arguments, an empty instance without any parameter or key * components is created. Use #set_pqg to manually set the parameters afterwards * (and optionally #set_key to set private and public key components). * This form is not compatible with OpenSSL 3.0 or later. * * If a String is given, tries to parse it as a DER- or PEM- encoded parameters. * See also OpenSSL::PKey.read which can parse keys of any kinds. * * The DH.new(size [, generator]) form is an alias of DH.generate. * * +string+:: * A String that contains the DER or PEM encoded key. * +size+:: * See DH.generate. * +generator+:: * See DH.generate. * * Examples: * # Creating an instance from scratch * # Note that this is deprecated and will result in ArgumentError when * # using OpenSSL 3.0 or later. * dh = OpenSSL::PKey::DH.new * dh.set_pqg(bn_p, nil, bn_g) * * # Generating a parameters and a key pair * dh = OpenSSL::PKey::DH.new(2048) # An alias of OpenSSL::PKey::DH.generate(2048) * * # Reading DH parameters from a PEM-encoded string * dh_params = OpenSSL::PKey::DH.new(File.read('parameters.pem')) # loads parameters only * dh = OpenSSL::PKey.generate_key(dh_params) # generates a key pair */ static VALUE ossl_dh_initialize(int argc, VALUE *argv, VALUE self) { EVP_PKEY *pkey; int type; DH *dh; BIO *in = NULL; VALUE arg; TypedData_Get_Struct(self, EVP_PKEY, &ossl_evp_pkey_type, pkey); if (pkey) rb_raise(rb_eTypeError, "pkey already initialized"); /* The DH.new(size, generator) form is handled by lib/openssl/pkey.rb */ if (rb_scan_args(argc, argv, "01", &arg) == 0) { #ifdef OSSL_HAVE_IMMUTABLE_PKEY rb_raise(rb_eArgError, "OpenSSL::PKey::DH.new cannot be called " \ "without arguments; pkeys are immutable with OpenSSL 3.0"); #else dh = DH_new(); if (!dh) ossl_raise(ePKeyError, "DH_new"); goto legacy; #endif } arg = ossl_to_der_if_possible(arg); in = ossl_obj2bio(&arg); /* * On OpenSSL <= 1.1.1 and current versions of LibreSSL, the generic * routine does not support DER-encoded parameters */ dh = d2i_DHparams_bio(in, NULL); if (dh) goto legacy; OSSL_BIO_reset(in); pkey = ossl_pkey_read_generic(in, Qnil); BIO_free(in); if (!pkey) ossl_raise(ePKeyError, "could not parse pkey"); type = EVP_PKEY_base_id(pkey); if (type != EVP_PKEY_DH) { EVP_PKEY_free(pkey); rb_raise(ePKeyError, "incorrect pkey type: %s", OBJ_nid2sn(type)); } RTYPEDDATA_DATA(self) = pkey; return self; legacy: BIO_free(in); pkey = EVP_PKEY_new(); if (!pkey || EVP_PKEY_assign_DH(pkey, dh) != 1) { EVP_PKEY_free(pkey); DH_free(dh); ossl_raise(ePKeyError, "EVP_PKEY_assign_DH"); } RTYPEDDATA_DATA(self) = pkey; return self; } #ifndef HAVE_EVP_PKEY_DUP /* :nodoc: */ static VALUE ossl_dh_initialize_copy(VALUE self, VALUE other) { EVP_PKEY *pkey; DH *dh, *dh_other; const BIGNUM *pub, *priv; TypedData_Get_Struct(self, EVP_PKEY, &ossl_evp_pkey_type, pkey); if (pkey) rb_raise(rb_eTypeError, "pkey already initialized"); GetDH(other, dh_other); dh = DHparams_dup(dh_other); if (!dh) ossl_raise(ePKeyError, "DHparams_dup"); 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(ePKeyError, "BN_dup"); } DH_set0_key(dh, pub2, priv2); } pkey = EVP_PKEY_new(); if (!pkey || EVP_PKEY_assign_DH(pkey, dh) != 1) { EVP_PKEY_free(pkey); DH_free(dh); ossl_raise(ePKeyError, "EVP_PKEY_assign_DH"); } RTYPEDDATA_DATA(self) = pkey; return self; } #endif /* * 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) { OSSL_3_const 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) { OSSL_3_const 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 *)dh)) ? Qtrue : Qfalse; #else return bn ? Qtrue : Qfalse; #endif } /* * call-seq: * dh.export -> aString * dh.to_pem -> aString * dh.to_s -> aString * * Serializes the DH parameters to a PEM-encoding. * * Note that any existing per-session public/private keys will *not* get * encoded, just the Diffie-Hellman parameters will be encoded. * * PEM-encoded parameters will look like: * * -----BEGIN DH PARAMETERS----- * [...] * -----END DH PARAMETERS----- * * See also #public_to_pem (X.509 SubjectPublicKeyInfo) and * #private_to_pem (PKCS #8 PrivateKeyInfo or EncryptedPrivateKeyInfo) for * serialization with the private or public key components. */ static VALUE ossl_dh_export(VALUE self) { OSSL_3_const DH *dh; BIO *out; VALUE str; GetDH(self, dh); if (!(out = BIO_new(BIO_s_mem()))) { ossl_raise(ePKeyError, NULL); } if (!PEM_write_bio_DHparams(out, dh)) { BIO_free(out); ossl_raise(ePKeyError, NULL); } str = ossl_membio2str(out); return str; } /* * call-seq: * dh.to_der -> aString * * Serializes the DH parameters to a DER-encoding * * Note that any existing per-session public/private keys will *not* get * encoded, just the Diffie-Hellman parameters will be encoded. * * See also #public_to_der (X.509 SubjectPublicKeyInfo) and * #private_to_der (PKCS #8 PrivateKeyInfo or EncryptedPrivateKeyInfo) for * serialization with the private or public key components. */ static VALUE ossl_dh_to_der(VALUE self) { OSSL_3_const DH *dh; unsigned char *p; long len; VALUE str; GetDH(self, dh); if((len = i2d_DHparams(dh, NULL)) <= 0) ossl_raise(ePKeyError, NULL); str = rb_str_new(0, len); p = (unsigned char *)RSTRING_PTR(str); if(i2d_DHparams(dh, &p) < 0) ossl_raise(ePKeyError, NULL); ossl_str_adjust(str, p); return str; } /* * 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. * * See also the man page EVP_PKEY_param_check(3). */ static VALUE ossl_dh_check_params(VALUE self) { int ret; #ifdef HAVE_EVP_PKEY_CHECK EVP_PKEY *pkey; EVP_PKEY_CTX *pctx; GetPKey(self, pkey); pctx = EVP_PKEY_CTX_new(pkey, /* engine */NULL); if (!pctx) ossl_raise(ePKeyError, "EVP_PKEY_CTX_new"); ret = EVP_PKEY_param_check(pctx); EVP_PKEY_CTX_free(pctx); #else DH *dh; int codes; GetDH(self, dh); ret = DH_check(dh, &codes) == 1 && codes == 0; #endif if (ret == 1) return Qtrue; else { /* DH_check_ex() will put error entry on failure */ ossl_clear_error(); return Qfalse; } } /* * Document-method: OpenSSL::PKey::DH#set_pqg * call-seq: * dh.set_pqg(p, q, g) -> self * * Sets _p_, _q_, _g_ to 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) { /* 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 * # you may send the parameters (der) and own public key (pub1) publicly * # to the participating party * dh1 = OpenSSL::PKey::DH.new(2048) * der = dh1.to_der * pub1 = dh1.pub_key * * # the other party generates its per-session key pair * dhparams = OpenSSL::PKey::DH.new(der) * dh2 = OpenSSL::PKey.generate_key(dhparams) * pub2 = dh2.pub_key * * symm_key1 = dh1.compute_key(pub2) * symm_key2 = dh2.compute_key(pub1) * puts symm_key1 == symm_key2 # => true */ cDH = rb_define_class_under(mPKey, "DH", cPKey); rb_define_method(cDH, "initialize", ossl_dh_initialize, -1); #ifndef HAVE_EVP_PKEY_DUP rb_define_method(cDH, "initialize_copy", ossl_dh_initialize_copy, 1); #endif rb_define_method(cDH, "public?", ossl_dh_is_public, 0); rb_define_method(cDH, "private?", ossl_dh_is_private, 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, "params_ok?", ossl_dh_check_params, 0); 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); } #else /* defined NO_DH */ void Init_ossl_dh(void) { } #endif /* NO_DH */ ================================================ FILE: ext/openssl/ossl_pkey_dsa.c ================================================ /* * 'OpenSSL for Ruby' project * Copyright (C) 2001-2002 Michal Rokos * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #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); \ if ((dsa) == NULL) \ ossl_raise(ePKeyError, "failed to get DSA from EVP_PKEY"); \ } while (0) static inline int DSA_HAS_PRIVATE(OSSL_3_const DSA *dsa) { const BIGNUM *bn; DSA_get0_key(dsa, NULL, &bn); return !!bn; } static inline int DSA_PRIVATE(VALUE obj, OSSL_3_const DSA *dsa) { return DSA_HAS_PRIVATE(dsa) || OSSL_PKEY_IS_PRIVATE(obj); } /* * Classes */ VALUE cDSA; /* * Private */ /* * call-seq: * DSA.new -> dsa * DSA.new(string [, pass]) -> dsa * DSA.new(size) -> dsa * * Creates a new DSA instance by reading an existing key from _string_. * * If called without arguments, creates a new instance with no key components * set. They can be set individually by #set_pqg and #set_key. * This form is not compatible with OpenSSL 3.0 or later. * * If called with a String, tries to parse as DER or PEM encoding of a \DSA key. * See also OpenSSL::PKey.read which can parse keys of any kinds. * * If called with a number, generates random parameters and a key pair. This * form works as an alias of DSA.generate. * * +string+:: * A String that contains a DER or PEM encoded key. * +pass+:: * A String that contains an optional password. * +size+:: * See DSA.generate. * * Examples: * p OpenSSL::PKey::DSA.new(1024) * #=> # * * p OpenSSL::PKey::DSA.new(File.read('dsa.pem')) * #=> # * * p OpenSSL::PKey::DSA.new(File.read('dsa.pem'), 'mypassword') * #=> # */ static VALUE ossl_dsa_initialize(int argc, VALUE *argv, VALUE self) { EVP_PKEY *pkey; DSA *dsa; BIO *in = NULL; VALUE arg, pass; int type; TypedData_Get_Struct(self, EVP_PKEY, &ossl_evp_pkey_type, pkey); if (pkey) rb_raise(rb_eTypeError, "pkey already initialized"); /* The DSA.new(size, generator) form is handled by lib/openssl/pkey.rb */ rb_scan_args(argc, argv, "02", &arg, &pass); if (argc == 0) { #ifdef OSSL_HAVE_IMMUTABLE_PKEY rb_raise(rb_eArgError, "OpenSSL::PKey::DSA.new cannot be called " \ "without arguments; pkeys are immutable with OpenSSL 3.0"); #else dsa = DSA_new(); if (!dsa) ossl_raise(ePKeyError, "DSA_new"); goto legacy; #endif } pass = ossl_pem_passwd_value(pass); arg = ossl_to_der_if_possible(arg); in = ossl_obj2bio(&arg); /* DER-encoded DSAPublicKey format isn't supported by the generic routine */ dsa = (DSA *)PEM_ASN1_read_bio((d2i_of_void *)d2i_DSAPublicKey, PEM_STRING_DSA_PUBLIC, in, NULL, NULL, NULL); if (dsa) goto legacy; OSSL_BIO_reset(in); pkey = ossl_pkey_read_generic(in, pass); BIO_free(in); if (!pkey) ossl_raise(ePKeyError, "Neither PUB key nor PRIV key"); type = EVP_PKEY_base_id(pkey); if (type != EVP_PKEY_DSA) { EVP_PKEY_free(pkey); rb_raise(ePKeyError, "incorrect pkey type: %s", OBJ_nid2sn(type)); } RTYPEDDATA_DATA(self) = pkey; return self; legacy: BIO_free(in); pkey = EVP_PKEY_new(); if (!pkey || EVP_PKEY_assign_DSA(pkey, dsa) != 1) { EVP_PKEY_free(pkey); DSA_free(dsa); ossl_raise(ePKeyError, "EVP_PKEY_assign_DSA"); } RTYPEDDATA_DATA(self) = pkey; return self; } #ifndef HAVE_EVP_PKEY_DUP /* :nodoc: */ static VALUE ossl_dsa_initialize_copy(VALUE self, VALUE other) { EVP_PKEY *pkey; DSA *dsa, *dsa_new; TypedData_Get_Struct(self, EVP_PKEY, &ossl_evp_pkey_type, pkey); if (pkey) rb_raise(rb_eTypeError, "pkey already initialized"); GetDSA(other, dsa); dsa_new = (DSA *)ASN1_dup((i2d_of_void *)i2d_DSAPrivateKey, (d2i_of_void *)d2i_DSAPrivateKey, (char *)dsa); if (!dsa_new) ossl_raise(ePKeyError, "ASN1_dup"); pkey = EVP_PKEY_new(); if (!pkey || EVP_PKEY_assign_DSA(pkey, dsa_new) != 1) { EVP_PKEY_free(pkey); DSA_free(dsa_new); ossl_raise(ePKeyError, "EVP_PKEY_assign_DSA"); } RTYPEDDATA_DATA(self) = pkey; return self; } #endif /* * 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) { const 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) { OSSL_3_const 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 * * Serializes a private or public key to a PEM-encoding. * * [When the key contains public components only] * * Serializes it into an X.509 SubjectPublicKeyInfo. * The parameters _cipher_ and _password_ are ignored. * * A PEM-encoded key will look like: * * -----BEGIN PUBLIC KEY----- * [...] * -----END PUBLIC KEY----- * * Consider using #public_to_pem instead. This serializes the key into an * X.509 SubjectPublicKeyInfo regardless of whether it is a public key * or a private key. * * [When the key contains private components, and no parameters are given] * * Serializes it into a traditional \OpenSSL DSAPrivateKey. * * A PEM-encoded key will look like: * * -----BEGIN DSA PRIVATE KEY----- * [...] * -----END DSA PRIVATE KEY----- * * [When the key contains private components, and _cipher_ and _password_ are given] * * Serializes it into a traditional \OpenSSL DSAPrivateKey and encrypts it in * OpenSSL's traditional PEM encryption format. * _cipher_ must be a cipher name understood by OpenSSL::Cipher.new or an * instance of OpenSSL::Cipher. * * An encrypted PEM-encoded key will look like: * * -----BEGIN DSA PRIVATE KEY----- * Proc-Type: 4,ENCRYPTED * DEK-Info: AES-128-CBC,733F5302505B34701FC41F5C0746E4C0 * * [...] * -----END DSA PRIVATE KEY----- * * Note that this format uses MD5 to derive the encryption key, and hence * will not be available on FIPS-compliant systems. * * This method is kept for compatibility. * This should only be used when the traditional, non-standard \OpenSSL format * is required. * * Consider using #public_to_pem (X.509 SubjectPublicKeyInfo) or #private_to_pem * (PKCS #8 PrivateKeyInfo or EncryptedPrivateKeyInfo) instead. */ static VALUE ossl_dsa_export(int argc, VALUE *argv, VALUE self) { OSSL_3_const DSA *dsa; GetDSA(self, dsa); if (DSA_HAS_PRIVATE(dsa)) return ossl_pkey_export_traditional(argc, argv, self, 0); else return ossl_pkey_export_spki(self, 0); } /* * call-seq: * dsa.to_der -> aString * * Serializes a private or public key to a DER-encoding. * * See #to_pem for details. * * This method is kept for compatibility. * This should only be used when the traditional, non-standard \OpenSSL format * is required. * * Consider using #public_to_der or #private_to_der instead. */ static VALUE ossl_dsa_to_der(VALUE self) { OSSL_3_const DSA *dsa; GetDSA(self, dsa); if (DSA_HAS_PRIVATE(dsa)) return ossl_pkey_export_traditional(0, NULL, self, 1); else return ossl_pkey_export_spki(self, 1); } /* * Document-method: OpenSSL::PKey::DSA#set_pqg * call-seq: * dsa.set_pqg(p, q, g) -> self * * Sets _p_, _q_, _g_ to 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) { /* 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. */ cDSA = rb_define_class_under(mPKey, "DSA", cPKey); rb_define_method(cDSA, "initialize", ossl_dsa_initialize, -1); #ifndef HAVE_EVP_PKEY_DUP rb_define_method(cDSA, "initialize_copy", ossl_dsa_initialize_copy, 1); #endif rb_define_method(cDSA, "public?", ossl_dsa_is_public, 0); rb_define_method(cDSA, "private?", ossl_dsa_is_private, 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); 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); } #else /* defined NO_DSA */ void Init_ossl_dsa(void) { } #endif /* NO_DSA */ ================================================ FILE: ext/openssl/ossl_pkey_ec.c ================================================ /* * Copyright (C) 2006-2007 Technorama Ltd. */ #include "ossl.h" #if !defined(OPENSSL_NO_EC) #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); \ if ((key) == NULL) \ ossl_raise(ePKeyError, "failed to get EC_KEY from EVP_PKEY"); \ } 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 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 GetECPointGroup(obj, group) do { \ VALUE _group = rb_attr_get(obj, id_i_group); \ GetECGroup(_group, group); \ } while (0) VALUE cEC; static VALUE cEC_GROUP; static VALUE eEC_GROUP; static VALUE cEC_POINT; static VALUE eEC_POINT; static VALUE sym_GFp, sym_GF2m; static VALUE sym_uncompressed, sym_compressed, sym_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); /* * 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; GetECGroup(arg, group); if (!(ec = EC_KEY_new())) ossl_raise(ePKeyError, NULL); if (!EC_KEY_set_group(ec, group)) { EC_KEY_free(ec); ossl_raise(ePKeyError, NULL); } } else { int nid = OBJ_sn2nid(StringValueCStr(arg)); if (nid == NID_undef) ossl_raise(ePKeyError, "invalid curve name"); if (!(ec = EC_KEY_new_by_curve_name(nid))) ossl_raise(ePKeyError, 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) { EVP_PKEY *pkey; EC_KEY *ec; VALUE obj; obj = rb_obj_alloc(klass); ec = ec_key_new_from_group(arg); pkey = EVP_PKEY_new(); if (!pkey || EVP_PKEY_assign_EC_KEY(pkey, ec) != 1) { EVP_PKEY_free(pkey); EC_KEY_free(ec); ossl_raise(ePKeyError, "EVP_PKEY_assign_EC_KEY"); } RTYPEDDATA_DATA(obj) = pkey; if (!EC_KEY_generate_key(ec)) ossl_raise(ePKeyError, "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; BIO *in; VALUE arg, pass; int type; TypedData_Get_Struct(self, EVP_PKEY, &ossl_evp_pkey_type, pkey); if (pkey) rb_raise(rb_eTypeError, "pkey already initialized"); rb_scan_args(argc, argv, "02", &arg, &pass); if (NIL_P(arg)) { #ifdef OSSL_HAVE_IMMUTABLE_PKEY rb_raise(rb_eArgError, "OpenSSL::PKey::EC.new cannot be called " \ "without arguments; pkeys are immutable with OpenSSL 3.0"); #else if (!(ec = EC_KEY_new())) ossl_raise(ePKeyError, "EC_KEY_new"); goto legacy; #endif } else if (rb_obj_is_kind_of(arg, cEC_GROUP)) { ec = ec_key_new_from_group(arg); goto legacy; } pass = ossl_pem_passwd_value(pass); arg = ossl_to_der_if_possible(arg); in = ossl_obj2bio(&arg); pkey = ossl_pkey_read_generic(in, pass); BIO_free(in); if (!pkey) { ossl_clear_error(); ec = ec_key_new_from_group(arg); goto legacy; } type = EVP_PKEY_base_id(pkey); if (type != EVP_PKEY_EC) { EVP_PKEY_free(pkey); rb_raise(ePKeyError, "incorrect pkey type: %s", OBJ_nid2sn(type)); } RTYPEDDATA_DATA(self) = pkey; return self; legacy: pkey = EVP_PKEY_new(); if (!pkey || EVP_PKEY_assign_EC_KEY(pkey, ec) != 1) { EVP_PKEY_free(pkey); EC_KEY_free(ec); ossl_raise(ePKeyError, "EVP_PKEY_assign_EC_KEY"); } RTYPEDDATA_DATA(self) = pkey; return self; } #ifndef HAVE_EVP_PKEY_DUP /* :nodoc: */ static VALUE ossl_ec_key_initialize_copy(VALUE self, VALUE other) { EVP_PKEY *pkey; EC_KEY *ec, *ec_new; TypedData_Get_Struct(self, EVP_PKEY, &ossl_evp_pkey_type, pkey); if (pkey) rb_raise(rb_eTypeError, "pkey already initialized"); GetEC(other, ec); ec_new = EC_KEY_dup(ec); if (!ec_new) ossl_raise(ePKeyError, "EC_KEY_dup"); pkey = EVP_PKEY_new(); if (!pkey || EVP_PKEY_assign_EC_KEY(pkey, ec_new) != 1) { EC_KEY_free(ec_new); ossl_raise(ePKeyError, "EVP_PKEY_assign_EC_KEY"); } RTYPEDDATA_DATA(self) = pkey; return self; } #endif /* * 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) { OSSL_3_const 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) { #ifdef OSSL_HAVE_IMMUTABLE_PKEY rb_raise(ePKeyError, "pkeys are immutable on OpenSSL 3.0"); #else EC_KEY *ec; EC_GROUP *group; GetEC(self, ec); GetECGroup(group_v, group); if (EC_KEY_set_group(ec, group) != 1) ossl_raise(ePKeyError, "EC_KEY_set_group"); return group_v; #endif } /* * 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) { OSSL_3_const 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) { #ifdef OSSL_HAVE_IMMUTABLE_PKEY rb_raise(ePKeyError, "pkeys are immutable on OpenSSL 3.0"); #else 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; /* fallthrough */ default: ossl_raise(ePKeyError, "EC_KEY_set_private_key"); } return private_key; #endif } /* * 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) { OSSL_3_const 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) { #ifdef OSSL_HAVE_IMMUTABLE_PKEY rb_raise(ePKeyError, "pkeys are immutable on OpenSSL 3.0"); #else EC_KEY *ec; EC_POINT *point = NULL; GetEC(self, ec); if (!NIL_P(public_key)) GetECPoint(public_key, point); switch (EC_KEY_set_public_key(ec, point)) { case 1: break; case 0: if (point == NULL) break; /* fallthrough */ default: ossl_raise(ePKeyError, "EC_KEY_set_public_key"); } return public_key; #endif } /* * 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) { OSSL_3_const 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) { OSSL_3_const EC_KEY *ec; GetEC(self, ec); return EC_KEY_get0_private_key(ec) ? Qtrue : Qfalse; } /* * call-seq: * key.export([cipher, password]) => String * key.to_pem([cipher, password]) => String * * Serializes a private or public key to a PEM-encoding. * * [When the key contains public components only] * * Serializes it into an X.509 SubjectPublicKeyInfo. * The parameters _cipher_ and _password_ are ignored. * * A PEM-encoded key will look like: * * -----BEGIN PUBLIC KEY----- * [...] * -----END PUBLIC KEY----- * * Consider using #public_to_pem instead. This serializes the key into an * X.509 SubjectPublicKeyInfo regardless of whether it is a public key * or a private key. * * [When the key contains private components, and no parameters are given] * * Serializes it into a SEC 1/RFC 5915 ECPrivateKey. * * A PEM-encoded key will look like: * * -----BEGIN EC PRIVATE KEY----- * [...] * -----END EC PRIVATE KEY----- * * [When the key contains private components, and _cipher_ and _password_ are given] * * Serializes it into a SEC 1/RFC 5915 ECPrivateKey * and encrypts it in OpenSSL's traditional PEM encryption format. * _cipher_ must be a cipher name understood by OpenSSL::Cipher.new or an * instance of OpenSSL::Cipher. * * An encrypted PEM-encoded key will look like: * * -----BEGIN EC PRIVATE KEY----- * Proc-Type: 4,ENCRYPTED * DEK-Info: AES-128-CBC,733F5302505B34701FC41F5C0746E4C0 * * [...] * -----END EC PRIVATE KEY----- * * Note that this format uses MD5 to derive the encryption key, and hence * will not be available on FIPS-compliant systems. * * This method is kept for compatibility. * This should only be used when the SEC 1/RFC 5915 ECPrivateKey format is * required. * * Consider using #public_to_pem (X.509 SubjectPublicKeyInfo) or #private_to_pem * (PKCS #8 PrivateKeyInfo or EncryptedPrivateKeyInfo) instead. */ static VALUE ossl_ec_key_export(int argc, VALUE *argv, VALUE self) { OSSL_3_const EC_KEY *ec; GetEC(self, ec); if (EC_KEY_get0_public_key(ec) == NULL) ossl_raise(ePKeyError, "can't export - no public key set"); if (EC_KEY_get0_private_key(ec)) return ossl_pkey_export_traditional(argc, argv, self, 0); else return ossl_pkey_export_spki(self, 0); } /* * call-seq: * key.to_der => String * * Serializes a private or public key to a DER-encoding. * * See #to_pem for details. * * This method is kept for compatibility. * This should only be used when the SEC 1/RFC 5915 ECPrivateKey format is * required. * * Consider using #public_to_der or #private_to_der instead. */ static VALUE ossl_ec_key_to_der(VALUE self) { OSSL_3_const EC_KEY *ec; GetEC(self, ec); if (EC_KEY_get0_public_key(ec) == NULL) ossl_raise(ePKeyError, "can't export - no public key set"); if (EC_KEY_get0_private_key(ec)) return ossl_pkey_export_traditional(0, NULL, self, 1); else return ossl_pkey_export_spki(self, 1); } /* * 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 # => # */ static VALUE ossl_ec_key_generate_key(VALUE self) { #ifdef OSSL_HAVE_IMMUTABLE_PKEY rb_raise(ePKeyError, "pkeys are immutable on OpenSSL 3.0"); #else EC_KEY *ec; GetEC(self, ec); if (EC_KEY_generate_key(ec) != 1) ossl_raise(ePKeyError, "EC_KEY_generate_key"); return self; #endif } /* * call-seq: * key.check_key => true * * Raises an exception if the key is invalid. * * See also the man page EVP_PKEY_public_check(3). */ static VALUE ossl_ec_key_check_key(VALUE self) { #ifdef HAVE_EVP_PKEY_CHECK EVP_PKEY *pkey; EVP_PKEY_CTX *pctx; const EC_KEY *ec; GetPKey(self, pkey); GetEC(self, ec); pctx = EVP_PKEY_CTX_new(pkey, /* engine */NULL); if (!pctx) ossl_raise(ePKeyError, "EVP_PKEY_CTX_new"); if (EC_KEY_get0_private_key(ec) != NULL) { if (EVP_PKEY_check(pctx) != 1) { EVP_PKEY_CTX_free(pctx); ossl_raise(ePKeyError, "EVP_PKEY_check"); } } else { if (EVP_PKEY_public_check(pctx) != 1) { EVP_PKEY_CTX_free(pctx); ossl_raise(ePKeyError, "EVP_PKEY_public_check"); } } EVP_PKEY_CTX_free(pctx); #else EC_KEY *ec; GetEC(self, ec); if (EC_KEY_check_key(ec) != 1) ossl_raise(ePKeyError, "EC_KEY_check_key"); #endif return Qtrue; } /* * OpenSSL::PKey::EC::Group */ static void ossl_ec_group_free(void *ptr) { EC_GROUP_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 | RUBY_TYPED_WB_PROTECTED, }; 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(: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. * * 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 (rb_obj_is_kind_of(arg1, cEC_GROUP)) { const EC_GROUP *arg1_group; GetECGroup(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); #if !defined(OPENSSL_IS_AWSLC) group = EC_GROUP_new_by_curve_name(nid); #else /* EC_GROUPs are static and immutable by default in AWS-LC. */ group = EC_GROUP_new_by_curve_name_mutable(nid); #endif 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)) { 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 (arg1 == sym_GFp) { new_curve = EC_GROUP_new_curve_GFp; } #if !defined(OPENSSL_NO_EC2M) else if (arg1 == sym_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 (given %d, expected 1 or 4)", argc); } ASSUME(group); RTYPEDDATA_DATA(self) = group; return self; } /* :nodoc: */ 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"); GetECGroup(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); GetECGroup(b, group2); switch (EC_GROUP_cmp(group1, group2, ossl_bn_ctx)) { case 0: return Qtrue; case 1: return Qfalse; default: ossl_raise(eEC_GROUP, "EC_GROUP_cmp"); } } /* * 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); GetECPoint(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; GetECGroup(self, group); bn_obj = ossl_bn_new(BN_value_one()); 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; GetECGroup(self, group); bn_obj = ossl_bn_new(BN_value_one()); 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 or nil * * Returns the curve name (short name) corresponding to this group, or +nil+ * if \OpenSSL does not have an OID associated with the group. * * See the OpenSSL documentation for EC_GROUP_get_curve_name() */ static VALUE ossl_ec_group_get_curve_name(VALUE self) { EC_GROUP *group; int nid; GetECGroup(self, group); nid = EC_GROUP_get_curve_name(group); if (nid == NID_undef) return Qnil; return rb_str_new_cstr(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; point_conversion_form_t form; GetECGroup(self, group); form = EC_GROUP_get_point_conversion_form(group); switch (form) { case POINT_CONVERSION_UNCOMPRESSED: return sym_uncompressed; case POINT_CONVERSION_COMPRESSED: return sym_compressed; case POINT_CONVERSION_HYBRID: return sym_hybrid; default: ossl_raise(eEC_GROUP, "unsupported point conversion form: %d, " \ "this module should be updated", form); } } static point_conversion_form_t parse_point_conversion_form_symbol(VALUE sym) { if (sym == sym_uncompressed) return POINT_CONVERSION_UNCOMPRESSED; if (sym == sym_compressed) return POINT_CONVERSION_COMPRESSED; if (sym == sym_hybrid) return POINT_CONVERSION_HYBRID; 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(ePKeyError, 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 | RUBY_TYPED_WB_PROTECTED, }; 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; } static VALUE ossl_ec_point_initialize_copy(VALUE, VALUE); /* * call-seq: * OpenSSL::PKey::EC::Point.new(point) * OpenSSL::PKey::EC::Point.new(group [, encoded_point]) * * Creates a new instance of OpenSSL::PKey::EC::Point. If the only argument is * an instance of EC::Point, a copy is returned. Otherwise, creates a point * that belongs to _group_. * * _encoded_point_ is the octet string representation of the point. This * must be either a String or an OpenSSL::BN. */ static VALUE ossl_ec_point_initialize(int argc, VALUE *argv, VALUE self) { EC_POINT *point; VALUE group_v, arg2; const EC_GROUP *group; TypedData_Get_Struct(self, EC_POINT, &ossl_ec_point_type, point); if (point) rb_raise(eEC_POINT, "EC_POINT already initialized"); rb_scan_args(argc, argv, "11", &group_v, &arg2); if (rb_obj_is_kind_of(group_v, cEC_POINT)) { if (argc != 1) rb_raise(rb_eArgError, "invalid second argument"); return ossl_ec_point_initialize_copy(self, group_v); } GetECGroup(group_v, group); if (argc == 1) { point = EC_POINT_new(group); if (!point) ossl_raise(eEC_POINT, "EC_POINT_new"); } else { if (rb_obj_is_kind_of(arg2, cBN)) { point = EC_POINT_bn2point(group, GetBNPtr(arg2), NULL, ossl_bn_ctx); if (!point) ossl_raise(eEC_POINT, "EC_POINT_bn2point"); } else { StringValue(arg2); point = EC_POINT_new(group); if (!point) ossl_raise(eEC_POINT, "EC_POINT_new"); if (!EC_POINT_oct2point(group, point, (unsigned char *)RSTRING_PTR(arg2), RSTRING_LEN(arg2), ossl_bn_ctx)) { EC_POINT_free(point); ossl_raise(eEC_POINT, "EC_POINT_oct2point"); } } } RTYPEDDATA_DATA(self) = point; rb_ivar_set(self, id_i_group, group_v); return self; } /* :nodoc: */ 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"); GetECPoint(other, point); group_v = rb_obj_dup(rb_attr_get(other, id_i_group)); GetECGroup(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); GetECPoint(b, point2); GetECGroup(group_v1, group); switch (EC_POINT_cmp(group, point1, point2, ossl_bn_ctx)) { case 0: return Qtrue; case 1: return Qfalse; default: ossl_raise(eEC_POINT, "EC_POINT_cmp"); } UNREACHABLE; } /* * 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(eEC_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(eEC_POINT, "EC_POINT_is_on_curve"); } UNREACHABLE; } /* * call-seq: * point.make_affine! => self * * This method is deprecated and should not be used. This is a no-op. */ static VALUE ossl_ec_point_make_affine(VALUE self) { EC_POINT *point; const EC_GROUP *group; GetECPoint(self, point); GetECPointGroup(self, group); rb_warn("OpenSSL::PKey::EC::Point#make_affine! is deprecated"); #if !defined(OSSL_HAVE_IMMUTABLE_PKEY) && !defined(OPENSSL_IS_AWSLC) if (EC_POINT_make_affine(group, point, ossl_bn_ctx) != 1) ossl_raise(eEC_POINT, "EC_POINT_make_affine"); #endif 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(eEC_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(eEC_POINT, "EC_POINT_set_to_infinity"); return self; } /* * call-seq: * point.to_octet_string(conversion_form) -> String * * Returns the octet string representation of the elliptic curve point. * * _conversion_form_ specifies how the point is converted. Possible values are: * * - +:compressed+ * - +:uncompressed+ * - +:hybrid+ */ static VALUE ossl_ec_point_to_octet_string(VALUE self, VALUE conversion_form) { EC_POINT *point; const EC_GROUP *group; point_conversion_form_t form; VALUE str; size_t len; GetECPoint(self, point); GetECPointGroup(self, group); form = parse_point_conversion_form_symbol(conversion_form); len = EC_POINT_point2oct(group, point, form, NULL, 0, ossl_bn_ctx); if (!len) ossl_raise(eEC_POINT, "EC_POINT_point2oct"); str = rb_str_new(NULL, (long)len); if (!EC_POINT_point2oct(group, point, form, (unsigned char *)RSTRING_PTR(str), len, ossl_bn_ctx)) ossl_raise(eEC_POINT, "EC_POINT_point2oct"); return str; } /* * call-seq: * point.add(point) => point * * Performs elliptic curve point addition. */ static VALUE ossl_ec_point_add(VALUE self, VALUE other) { EC_POINT *point_self, *point_other, *point_result; const EC_GROUP *group; VALUE group_v = rb_attr_get(self, id_i_group); VALUE result; GetECPoint(self, point_self); GetECPoint(other, point_other); GetECGroup(group_v, group); result = rb_obj_alloc(cEC_POINT); ossl_ec_point_initialize(1, &group_v, result); GetECPoint(result, point_result); if (EC_POINT_add(group, point_result, point_self, point_other, ossl_bn_ctx) != 1) { ossl_raise(eEC_POINT, "EC_POINT_add"); } return result; } /* * call-seq: * point.mul(bn1 [, bn2]) => point * * Performs elliptic curve point multiplication. * * The first form calculates bn1 * point + bn2 * G, where +G+ is the * generator of the group of _point_. _bn2_ may be omitted, and in that case, * the result is just bn1 * point. * * Before version 4.0.0, and when compiled with OpenSSL 1.1.1 or older, this * method allowed another form: * point.mul(bns, points [, bn2]) => point */ 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); GetECGroup(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) || argc > 2) rb_raise(rb_eNotImpError, "OpenSSL::PKey::EC::Point#mul with arrays " \ "is no longer supported"); 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); return result; } void Init_ossl_ec(void) { #undef rb_intern /* * 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); sym_GFp = ID2SYM(rb_intern_const("GFp")); sym_GF2m = ID2SYM(rb_intern_const("GF2m")); sym_uncompressed = ID2SYM(rb_intern_const("uncompressed")); sym_compressed = ID2SYM(rb_intern_const("compressed")); sym_hybrid = ID2SYM(rb_intern_const("hybrid")); rb_define_const(cEC, "NAMED_CURVE", INT2NUM(OPENSSL_EC_NAMED_CURVE)); rb_define_const(cEC, "EXPLICIT_CURVE", INT2NUM(OPENSSL_EC_EXPLICIT_CURVE)); 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); #ifndef HAVE_EVP_PKEY_DUP rb_define_method(cEC, "initialize_copy", ossl_ec_key_initialize_copy, 1); #endif 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, "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_alloc_func(cEC_GROUP, ossl_ec_group_alloc); rb_define_method(cEC_GROUP, "initialize", ossl_ec_group_initialize, -1); rb_define_method(cEC_GROUP, "initialize_copy", ossl_ec_group_initialize_copy, 1); 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_method(cEC_POINT, "initialize_copy", ossl_ec_point_initialize_copy, 1); 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_octet_string", ossl_ec_point_to_octet_string, 1); rb_define_method(cEC_POINT, "add", ossl_ec_point_add, 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 */ ================================================ FILE: ext/openssl/ossl_pkey_rsa.c ================================================ /* * 'OpenSSL for Ruby' project * Copyright (C) 2001-2002 Michal Rokos * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #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); \ if ((rsa) == NULL) \ ossl_raise(ePKeyError, "failed to get RSA from EVP_PKEY"); \ } while (0) static inline int RSA_HAS_PRIVATE(OSSL_3_const RSA *rsa) { const BIGNUM *e, *d; RSA_get0_key(rsa, NULL, &e, &d); return e && d; } static inline int RSA_PRIVATE(VALUE obj, OSSL_3_const RSA *rsa) { return RSA_HAS_PRIVATE(rsa) || OSSL_PKEY_IS_PRIVATE(obj); } /* * Classes */ VALUE cRSA; /* * Private */ /* * call-seq: * RSA.new -> rsa * RSA.new(encoded_key [, password ]) -> rsa * RSA.new(encoded_key) { password } -> rsa * RSA.new(size [, exponent]) -> rsa * * Generates or loads an \RSA keypair. * * If called without arguments, creates a new instance with no key components * set. They can be set individually by #set_key, #set_factors, and * #set_crt_params. * This form is not compatible with OpenSSL 3.0 or later. * * If called with a String, tries to parse as DER or PEM encoding of an \RSA key. * Note that if _password_ is not specified, but the key is encrypted with a * password, \OpenSSL will prompt for it. * See also OpenSSL::PKey.read which can parse keys of any kind. * * If called with a number, generates a new key pair. This form works as an * alias of RSA.generate. * * Examples: * OpenSSL::PKey::RSA.new 2048 * OpenSSL::PKey::RSA.new File.read 'rsa.pem' * OpenSSL::PKey::RSA.new File.read('rsa.pem'), 'my password' */ static VALUE ossl_rsa_initialize(int argc, VALUE *argv, VALUE self) { EVP_PKEY *pkey; RSA *rsa; BIO *in = NULL; VALUE arg, pass; int type; TypedData_Get_Struct(self, EVP_PKEY, &ossl_evp_pkey_type, pkey); if (pkey) rb_raise(rb_eTypeError, "pkey already initialized"); /* The RSA.new(size, generator) form is handled by lib/openssl/pkey.rb */ rb_scan_args(argc, argv, "02", &arg, &pass); if (argc == 0) { #ifdef OSSL_HAVE_IMMUTABLE_PKEY rb_raise(rb_eArgError, "OpenSSL::PKey::RSA.new cannot be called " \ "without arguments; pkeys are immutable with OpenSSL 3.0"); #else rsa = RSA_new(); if (!rsa) ossl_raise(ePKeyError, "RSA_new"); goto legacy; #endif } pass = ossl_pem_passwd_value(pass); arg = ossl_to_der_if_possible(arg); in = ossl_obj2bio(&arg); /* First try RSAPublicKey format */ rsa = d2i_RSAPublicKey_bio(in, NULL); if (rsa) goto legacy; OSSL_BIO_reset(in); rsa = PEM_read_bio_RSAPublicKey(in, NULL, NULL, NULL); if (rsa) goto legacy; OSSL_BIO_reset(in); /* Use the generic routine */ pkey = ossl_pkey_read_generic(in, pass); BIO_free(in); if (!pkey) ossl_raise(ePKeyError, "Neither PUB key nor PRIV key"); type = EVP_PKEY_base_id(pkey); if (type != EVP_PKEY_RSA) { EVP_PKEY_free(pkey); rb_raise(ePKeyError, "incorrect pkey type: %s", OBJ_nid2sn(type)); } RTYPEDDATA_DATA(self) = pkey; return self; legacy: BIO_free(in); pkey = EVP_PKEY_new(); if (!pkey || EVP_PKEY_assign_RSA(pkey, rsa) != 1) { EVP_PKEY_free(pkey); RSA_free(rsa); ossl_raise(ePKeyError, "EVP_PKEY_assign_RSA"); } RTYPEDDATA_DATA(self) = pkey; return self; } #ifndef HAVE_EVP_PKEY_DUP /* :nodoc: */ static VALUE ossl_rsa_initialize_copy(VALUE self, VALUE other) { EVP_PKEY *pkey; RSA *rsa, *rsa_new; TypedData_Get_Struct(self, EVP_PKEY, &ossl_evp_pkey_type, pkey); if (pkey) rb_raise(rb_eTypeError, "pkey already initialized"); GetRSA(other, rsa); rsa_new = (RSA *)ASN1_dup((i2d_of_void *)i2d_RSAPrivateKey, (d2i_of_void *)d2i_RSAPrivateKey, (char *)rsa); if (!rsa_new) ossl_raise(ePKeyError, "ASN1_dup"); pkey = EVP_PKEY_new(); if (!pkey || EVP_PKEY_assign_RSA(pkey, rsa_new) != 1) { RSA_free(rsa_new); ossl_raise(ePKeyError, "EVP_PKEY_assign_RSA"); } RTYPEDDATA_DATA(self) = pkey; return self; } #endif /* * 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) { OSSL_3_const 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) { OSSL_3_const RSA *rsa; GetRSA(self, rsa); return RSA_PRIVATE(self, rsa) ? Qtrue : Qfalse; } static int can_export_rsaprivatekey(VALUE self) { OSSL_3_const RSA *rsa; 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); return n && e && d && p && q && dmp1 && dmq1 && iqmp; } /* * call-seq: * rsa.export([cipher, password]) => PEM-format String * rsa.to_pem([cipher, password]) => PEM-format String * rsa.to_s([cipher, password]) => PEM-format String * * Serializes a private or public key to a PEM-encoding. * * [When the key contains public components only] * * Serializes it into an X.509 SubjectPublicKeyInfo. * The parameters _cipher_ and _password_ are ignored. * * A PEM-encoded key will look like: * * -----BEGIN PUBLIC KEY----- * [...] * -----END PUBLIC KEY----- * * Consider using #public_to_pem instead. This serializes the key into an * X.509 SubjectPublicKeyInfo regardless of whether the key is a public key * or a private key. * * [When the key contains private components, and no parameters are given] * * Serializes it into a PKCS #1 RSAPrivateKey. * * A PEM-encoded key will look like: * * -----BEGIN RSA PRIVATE KEY----- * [...] * -----END RSA PRIVATE KEY----- * * [When the key contains private components, and _cipher_ and _password_ are given] * * Serializes it into a PKCS #1 RSAPrivateKey * and encrypts it in OpenSSL's traditional PEM encryption format. * _cipher_ must be a cipher name understood by OpenSSL::Cipher.new or an * instance of OpenSSL::Cipher. * * An encrypted PEM-encoded key will look like: * * -----BEGIN RSA PRIVATE KEY----- * Proc-Type: 4,ENCRYPTED * DEK-Info: AES-128-CBC,733F5302505B34701FC41F5C0746E4C0 * * [...] * -----END RSA PRIVATE KEY----- * * Note that this format uses MD5 to derive the encryption key, and hence * will not be available on FIPS-compliant systems. * * This method is kept for compatibility. * This should only be used when the PKCS #1 RSAPrivateKey format is required. * * Consider using #public_to_pem (X.509 SubjectPublicKeyInfo) or #private_to_pem * (PKCS #8 PrivateKeyInfo or EncryptedPrivateKeyInfo) instead. */ static VALUE ossl_rsa_export(int argc, VALUE *argv, VALUE self) { if (can_export_rsaprivatekey(self)) return ossl_pkey_export_traditional(argc, argv, self, 0); else return ossl_pkey_export_spki(self, 0); } /* * call-seq: * rsa.to_der => DER-format String * * Serializes a private or public key to a DER-encoding. * * See #to_pem for details. * * This method is kept for compatibility. * This should only be used when the PKCS #1 RSAPrivateKey format is required. * * Consider using #public_to_der or #private_to_der instead. */ static VALUE ossl_rsa_to_der(VALUE self) { if (can_export_rsaprivatekey(self)) return ossl_pkey_export_traditional(0, NULL, self, 1); else return ossl_pkey_export_spki(self, 1); } /* * call-seq: * rsa.sign_pss(digest, data, salt_length:, mgf1_hash:) -> String * * Signs _data_ using the Probabilistic Signature Scheme (RSA-PSS) and returns * the calculated signature. * * PKeyError will be raised if an error occurs. * * See #verify_pss for the verification operation. * * === Parameters * _digest_:: * A String containing the message digest algorithm name. * _data_:: * A String. The data to be signed. * _salt_length_:: * The length in octets of the salt. Two special values are reserved: * +:digest+ means the digest length, and +:max+ means the maximum possible * length for the combination of the private key and the selected message * digest algorithm. * _mgf1_hash_:: * The hash algorithm used in MGF1 (the currently supported mask generation * function (MGF)). * * === Example * data = "Sign me!" * pkey = OpenSSL::PKey::RSA.new(2048) * signature = pkey.sign_pss("SHA256", data, salt_length: :max, mgf1_hash: "SHA256") * pub_key = OpenSSL::PKey.read(pkey.public_to_der) * puts pub_key.verify_pss("SHA256", signature, data, * salt_length: :auto, mgf1_hash: "SHA256") # => true */ static VALUE ossl_rsa_sign_pss(int argc, VALUE *argv, VALUE self) { VALUE digest, data, options, kwargs[2], signature, mgf1md_holder, md_holder; static ID kwargs_ids[2]; EVP_PKEY *pkey; EVP_PKEY_CTX *pkey_ctx; const EVP_MD *md, *mgf1md; EVP_MD_CTX *md_ctx; size_t buf_len; int salt_len; if (!kwargs_ids[0]) { kwargs_ids[0] = rb_intern_const("salt_length"); kwargs_ids[1] = rb_intern_const("mgf1_hash"); } rb_scan_args(argc, argv, "2:", &digest, &data, &options); rb_get_kwargs(options, kwargs_ids, 2, 0, kwargs); if (kwargs[0] == ID2SYM(rb_intern("max"))) salt_len = -2; /* RSA_PSS_SALTLEN_MAX_SIGN */ else if (kwargs[0] == ID2SYM(rb_intern("digest"))) salt_len = -1; /* RSA_PSS_SALTLEN_DIGEST */ else salt_len = NUM2INT(kwargs[0]); mgf1md = ossl_evp_md_fetch(kwargs[1], &mgf1md_holder); pkey = GetPrivPKeyPtr(self); buf_len = EVP_PKEY_size(pkey); md = ossl_evp_md_fetch(digest, &md_holder); StringValue(data); signature = rb_str_new(NULL, (long)buf_len); md_ctx = EVP_MD_CTX_new(); if (!md_ctx) goto err; if (EVP_DigestSignInit(md_ctx, &pkey_ctx, md, NULL, pkey) != 1) goto err; if (EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING) != 1) goto err; if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, salt_len) != 1) goto err; if (EVP_PKEY_CTX_set_rsa_mgf1_md(pkey_ctx, mgf1md) != 1) goto err; if (EVP_DigestSignUpdate(md_ctx, RSTRING_PTR(data), RSTRING_LEN(data)) != 1) goto err; if (EVP_DigestSignFinal(md_ctx, (unsigned char *)RSTRING_PTR(signature), &buf_len) != 1) goto err; rb_str_set_len(signature, (long)buf_len); EVP_MD_CTX_free(md_ctx); return signature; err: EVP_MD_CTX_free(md_ctx); ossl_raise(ePKeyError, NULL); } /* * call-seq: * rsa.verify_pss(digest, signature, data, salt_length:, mgf1_hash:) -> true | false * * Verifies _data_ using the Probabilistic Signature Scheme (RSA-PSS). * * The return value is +true+ if the signature is valid, +false+ otherwise. * PKeyError will be raised if an error occurs. * * See #sign_pss for the signing operation and an example code. * * === Parameters * _digest_:: * A String containing the message digest algorithm name. * _data_:: * A String. The data to be signed. * _salt_length_:: * The length in octets of the salt. Two special values are reserved: * +:digest+ means the digest length, and +:auto+ means automatically * determining the length based on the signature. * _mgf1_hash_:: * The hash algorithm used in MGF1. */ static VALUE ossl_rsa_verify_pss(int argc, VALUE *argv, VALUE self) { VALUE digest, signature, data, options, kwargs[2], mgf1md_holder, md_holder; static ID kwargs_ids[2]; EVP_PKEY *pkey; EVP_PKEY_CTX *pkey_ctx; const EVP_MD *md, *mgf1md; EVP_MD_CTX *md_ctx; int result, salt_len; if (!kwargs_ids[0]) { kwargs_ids[0] = rb_intern_const("salt_length"); kwargs_ids[1] = rb_intern_const("mgf1_hash"); } rb_scan_args(argc, argv, "3:", &digest, &signature, &data, &options); rb_get_kwargs(options, kwargs_ids, 2, 0, kwargs); if (kwargs[0] == ID2SYM(rb_intern("auto"))) salt_len = -2; /* RSA_PSS_SALTLEN_AUTO */ else if (kwargs[0] == ID2SYM(rb_intern("digest"))) salt_len = -1; /* RSA_PSS_SALTLEN_DIGEST */ else salt_len = NUM2INT(kwargs[0]); mgf1md = ossl_evp_md_fetch(kwargs[1], &mgf1md_holder); GetPKey(self, pkey); md = ossl_evp_md_fetch(digest, &md_holder); StringValue(signature); StringValue(data); md_ctx = EVP_MD_CTX_new(); if (!md_ctx) goto err; if (EVP_DigestVerifyInit(md_ctx, &pkey_ctx, md, NULL, pkey) != 1) goto err; if (EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING) != 1) goto err; if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, salt_len) != 1) goto err; if (EVP_PKEY_CTX_set_rsa_mgf1_md(pkey_ctx, mgf1md) != 1) goto err; if (EVP_DigestVerifyUpdate(md_ctx, RSTRING_PTR(data), RSTRING_LEN(data)) != 1) goto err; result = EVP_DigestVerifyFinal(md_ctx, (unsigned char *)RSTRING_PTR(signature), RSTRING_LEN(signature)); EVP_MD_CTX_free(md_ctx); switch (result) { case 0: ossl_clear_error(); return Qfalse; case 1: return Qtrue; default: ossl_raise(ePKeyError, "EVP_DigestVerifyFinal"); } err: EVP_MD_CTX_free(md_ctx); ossl_raise(ePKeyError, NULL); } /* * 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 * d mod (p - 1), d mod (q - 1) and q^(-1) mod p * 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) { /* 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_method(cRSA, "initialize", ossl_rsa_initialize, -1); #ifndef HAVE_EVP_PKEY_DUP rb_define_method(cRSA, "initialize_copy", ossl_rsa_initialize_copy, 1); #endif rb_define_method(cRSA, "public?", ossl_rsa_is_public, 0); rb_define_method(cRSA, "private?", ossl_rsa_is_private, 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, "sign_pss", ossl_rsa_sign_pss, -1); rb_define_method(cRSA, "verify_pss", ossl_rsa_verify_pss, -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); /* * 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 */ ================================================ FILE: ext/openssl/ossl_provider.c ================================================ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #include "ossl.h" #ifdef OSSL_USE_PROVIDER #define NewProvider(klass) \ TypedData_Wrap_Struct((klass), &ossl_provider_type, 0) #define SetProvider(obj, provider) do { \ if (!(provider)) { \ ossl_raise(rb_eRuntimeError, "Provider wasn't initialized."); \ } \ RTYPEDDATA_DATA(obj) = (provider); \ } while(0) #define GetProvider(obj, provider) do { \ TypedData_Get_Struct((obj), OSSL_PROVIDER, &ossl_provider_type, (provider)); \ if (!(provider)) { \ ossl_raise(rb_eRuntimeError, "PROVIDER wasn't initialized."); \ } \ } while (0) static const rb_data_type_t ossl_provider_type = { "OpenSSL/Provider", { 0, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; /* * Classes */ /* Document-class: OpenSSL::Provider * * This class is the access to openssl's Provider * See also, https://www.openssl.org/docs/manmaster/man7/provider.html */ static VALUE cProvider; /* Document-class: OpenSSL::Provider::ProviderError * * This is the generic exception for OpenSSL::Provider related errors */ static VALUE eProviderError; /* * call-seq: * OpenSSL::Provider.load(name) -> provider * * This method loads and initializes a provider */ static VALUE ossl_provider_s_load(VALUE klass, VALUE name) { OSSL_PROVIDER *provider = NULL; VALUE obj; const char *provider_name_ptr = StringValueCStr(name); provider = OSSL_PROVIDER_load(NULL, provider_name_ptr); if (provider == NULL) { ossl_raise(eProviderError, "Failed to load %s provider", provider_name_ptr); } obj = NewProvider(klass); SetProvider(obj, provider); return obj; } struct ary_with_state { VALUE ary; int state; }; struct rb_push_provider_name_args { OSSL_PROVIDER *prov; VALUE ary; }; static VALUE rb_push_provider_name(VALUE rb_push_provider_name_args) { struct rb_push_provider_name_args *args = (struct rb_push_provider_name_args *)rb_push_provider_name_args; VALUE name = rb_str_new2(OSSL_PROVIDER_get0_name(args->prov)); return rb_ary_push(args->ary, name); } static int push_provider(OSSL_PROVIDER *prov, void *cbdata) { struct ary_with_state *ary_with_state = (struct ary_with_state *)cbdata; struct rb_push_provider_name_args args = { prov, ary_with_state->ary }; rb_protect(rb_push_provider_name, (VALUE)&args, &ary_with_state->state); if (ary_with_state->state) { return 0; } else { return 1; } } /* * call-seq: * OpenSSL::Provider.provider_names -> [provider_name, ...] * * Returns an array of currently loaded provider names. */ static VALUE ossl_provider_s_provider_names(VALUE klass) { VALUE ary = rb_ary_new(); struct ary_with_state cbdata = { ary, 0 }; int result = OSSL_PROVIDER_do_all(NULL, &push_provider, (void*)&cbdata); if (result != 1 ) { if (cbdata.state) { rb_jump_tag(cbdata.state); } else { ossl_raise(eProviderError, "Failed to load provider names"); } } return ary; } /* * call-seq: * provider.unload -> true * * This method unloads this provider. * * if provider unload fails or already unloaded, it raises OpenSSL::Provider::ProviderError */ static VALUE ossl_provider_unload(VALUE self) { OSSL_PROVIDER *prov; if (RTYPEDDATA_DATA(self) == NULL) { ossl_raise(eProviderError, "Provider already unloaded."); } GetProvider(self, prov); int result = OSSL_PROVIDER_unload(prov); if (result != 1) { ossl_raise(eProviderError, "Failed to unload provider"); } RTYPEDDATA_DATA(self) = NULL; return Qtrue; } /* * call-seq: * provider.name -> string * * Get the name of this provider. * * if this provider is already unloaded, it raises OpenSSL::Provider::ProviderError */ static VALUE ossl_provider_get_name(VALUE self) { OSSL_PROVIDER *prov; if (RTYPEDDATA_DATA(self) == NULL) { ossl_raise(eProviderError, "Provider already unloaded."); } GetProvider(self, prov); return rb_str_new2(OSSL_PROVIDER_get0_name(prov)); } /* * call-seq: * provider.inspect -> string * * Pretty prints this provider. */ static VALUE ossl_provider_inspect(VALUE self) { OSSL_PROVIDER *prov; if (RTYPEDDATA_DATA(self) == NULL ) { return rb_sprintf("#<%"PRIsVALUE" unloaded provider>", rb_obj_class(self)); } GetProvider(self, prov); return rb_sprintf("#<%"PRIsVALUE" name=\"%s\">", rb_obj_class(self), OSSL_PROVIDER_get0_name(prov)); } void Init_ossl_provider(void) { cProvider = rb_define_class_under(mOSSL, "Provider", rb_cObject); eProviderError = rb_define_class_under(cProvider, "ProviderError", eOSSLError); rb_undef_alloc_func(cProvider); rb_define_singleton_method(cProvider, "load", ossl_provider_s_load, 1); rb_define_singleton_method(cProvider, "provider_names", ossl_provider_s_provider_names, 0); rb_define_method(cProvider, "unload", ossl_provider_unload, 0); rb_define_method(cProvider, "name", ossl_provider_get_name, 0); rb_define_method(cProvider, "inspect", ossl_provider_inspect, 0); } #else void Init_ossl_provider(void) { } #endif ================================================ FILE: ext/openssl/ossl_provider.h ================================================ #if !defined(OSSL_PROVIDER_H) #define OSSL_PROVIDER_H void Init_ossl_provider(void); #endif ================================================ FILE: ext/openssl/ossl_rand.c ================================================ /* * 'OpenSSL for Ruby' project * Copyright (C) 2001-2002 Michal Rokos * * All rights reserved. * * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #include "ossl.h" static VALUE mRandom; static 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) { 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) { if (RAND_write_file(StringValueCStr(filename)) == -1) { ossl_raise(eRandomError, NULL); } return Qtrue; } /* * call-seq: * random_bytes(length) -> string * * Generates a 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; } #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) { 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); 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) { 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); rb_define_alias(rb_singleton_class(mRandom), "pseudo_bytes", "random_bytes"); #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); } ================================================ FILE: ext/openssl/ossl_rand.h ================================================ /* * 'OpenSSL for Ruby' project * Copyright (C) 2001-2002 Michal Rokos * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #if !defined(_OSSL_RAND_H_) #define _OSSL_RAND_H_ void Init_ossl_rand(void); #endif /* _OSSL_RAND_H_ */ ================================================ FILE: ext/openssl/ossl_ssl.c ================================================ /* * 'OpenSSL for Ruby' project * Copyright (C) 2000-2002 GOTOU Yuuzou * Copyright (C) 2001-2002 Michal Rokos * Copyright (C) 2001-2007 Technorama Ltd. * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #include "ossl.h" #ifndef OPENSSL_NO_SOCK #define numberof(ary) (int)(sizeof(ary)/sizeof((ary)[0])) #if !defined(OPENSSL_NO_NEXTPROTONEG) && !OSSL_IS_LIBRESSL # define OSSL_USE_NEXTPROTONEG #endif #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 eSSLError; static VALUE cSSLContext; VALUE cSSLSocket; static VALUE eSSLErrorWaitReadable; static VALUE eSSLErrorWaitWritable; static ID id_call, ID_callback_state, id_npn_protocols_encoded, id_each; 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_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, id_i_keylog_cb, id_i_tmp_dh_callback; static ID id_i_io, id_i_context, id_i_hostname, id_i_sync_close; static int ossl_ssl_ex_ptr_idx; static int ossl_sslctx_ex_ptr_idx; static void ossl_sslctx_mark(void *ptr) { SSL_CTX *ctx = ptr; rb_gc_mark((VALUE)SSL_CTX_get_ex_data(ctx, ossl_sslctx_ex_ptr_idx)); } static void ossl_sslctx_free(void *ptr) { SSL_CTX_free(ptr); } static const rb_data_type_t ossl_sslctx_type = { "OpenSSL/SSL/CTX", { ossl_sslctx_mark, ossl_sslctx_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; static VALUE ossl_sslctx_s_alloc(VALUE klass) { SSL_CTX *ctx; long mode = 0 | SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_RELEASE_BUFFERS; VALUE obj; obj = TypedData_Wrap_Struct(klass, &ossl_sslctx_type, 0); ctx = SSL_CTX_new(TLS_method()); if (!ctx) { ossl_raise(eSSLError, "SSL_CTX_new"); } SSL_CTX_set_mode(ctx, mode); SSL_CTX_set_dh_auto(ctx, 1); RTYPEDDATA_DATA(obj) = ctx; if (!SSL_CTX_set_ex_data(ctx, ossl_sslctx_ex_ptr_idx, (void *)obj)) ossl_raise(eSSLError, "SSL_CTX_set_ex_data"); return obj; } 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_funcallv(cb, id_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) struct tmp_dh_callback_args { VALUE ssl_obj; int is_export; int keylength; }; static VALUE ossl_call_tmp_dh_callback(VALUE arg) { struct tmp_dh_callback_args *args = (struct tmp_dh_callback_args *)arg; VALUE ctx_obj, cb, obj; const DH *dh; ctx_obj = rb_attr_get(args->ssl_obj, id_i_context); cb = rb_attr_get(ctx_obj, id_i_tmp_dh_callback); if (NIL_P(cb)) return (VALUE)NULL; obj = rb_funcall(cb, id_call, 3, args->ssl_obj, INT2NUM(args->is_export), INT2NUM(args->keylength)); // TODO: We should riase if obj is not DH dh = EVP_PKEY_get0_DH(GetPKeyPtr(obj)); if (!dh) ossl_clear_error(); return (VALUE)dh; } static DH * ossl_tmp_dh_callback(SSL *ssl, int is_export, int keylength) { int state; VALUE rb_ssl = (VALUE)SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx); struct tmp_dh_callback_args args = {rb_ssl, is_export, keylength}; VALUE ret = rb_protect(ossl_call_tmp_dh_callback, (VALUE)&args, &state); if (state) { rb_ivar_set(rb_ssl, ID_callback_state, INT2NUM(state)); return NULL; } return (DH *)ret; } #endif /* OPENSSL_NO_DH */ 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()); ssl_obj = (VALUE)SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx); sslctx_obj = rb_attr_get(ssl_obj, id_i_context); cb = rb_attr_get(sslctx_obj, id_i_verify_callback); 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; } if (ret != Qtrue) { preverify_ok = 0; X509_STORE_CTX_set_error(ctx, X509_V_ERR_HOSTNAME_MISMATCH); } } 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_funcallv(cb, id_call, 1, &ary); } static SSL_SESSION * ossl_sslctx_session_get_cb(SSL *ssl, const unsigned char *buf, int len, int *copy) { VALUE ary, ssl_obj, ret_obj; SSL_SESSION *sess; int state = 0; OSSL_Debug("SSL SESSION get callback entered"); ssl_obj = (VALUE)SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx); 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; GetSSLSession(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_funcallv(cb, id_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; int state = 0; OSSL_Debug("SSL SESSION new callback entered"); ssl_obj = (VALUE)SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx); 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; } #if !OSSL_IS_LIBRESSL /* * It is only compatible with OpenSSL >= 1.1.1. Even if LibreSSL implements * SSL_CTX_set_keylog_callback() from v3.4.2, it does nothing (see * https://github.com/libressl-portable/openbsd/commit/648d39f0f035835d0653342d139883b9661e9cb6). */ struct ossl_call_keylog_cb_args { VALUE ssl_obj; const char * line; }; static VALUE ossl_call_keylog_cb(VALUE args_v) { VALUE sslctx_obj, cb, line_v; struct ossl_call_keylog_cb_args *args = (struct ossl_call_keylog_cb_args *) args_v; sslctx_obj = rb_attr_get(args->ssl_obj, id_i_context); cb = rb_attr_get(sslctx_obj, id_i_keylog_cb); if (NIL_P(cb)) return Qnil; line_v = rb_str_new_cstr(args->line); return rb_funcall(cb, id_call, 2, args->ssl_obj, line_v); } static void ossl_sslctx_keylog_cb(const SSL *ssl, const char *line) { VALUE ssl_obj; struct ossl_call_keylog_cb_args args; int state = 0; OSSL_Debug("SSL keylog callback entered"); ssl_obj = (VALUE)SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx); args.ssl_obj = ssl_obj; args.line = line; rb_protect(ossl_call_keylog_cb, (VALUE)&args, &state); if (state) { rb_ivar_set(ssl_obj, ID_callback_state, INT2NUM(state)); } } #endif 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_funcallv(cb, id_call, 1, &ary); } static void ossl_sslctx_session_remove_cb(SSL_CTX *ctx, SSL_SESSION *sess) { VALUE ary, sslctx_obj, sess_obj; int state = 0; /* * This callback is also called for all sessions in the internal store * when SSL_CTX_free() is called. */ if (rb_during_gc()) return; OSSL_Debug("SSL SESSION remove callback entered"); sslctx_obj = (VALUE)SSL_CTX_get_ex_data(ctx, ossl_sslctx_ex_ptr_idx); 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)) { X509_free(x509); ossl_raise(eSSLError, "SSL_CTX_add_extra_chain_cert"); } return i; } static VALUE ossl_sslctx_setup(VALUE self); static VALUE ossl_call_servername_cb(VALUE arg) { SSL *ssl = (void *)arg; const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); if (!servername) return Qnil; VALUE ssl_obj = (VALUE)SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx); VALUE sslctx_obj = rb_attr_get(ssl_obj, id_i_context); VALUE cb = rb_attr_get(sslctx_obj, id_i_servername_cb); VALUE ary = rb_assoc_new(ssl_obj, rb_str_new_cstr(servername)); VALUE ret_obj = rb_funcallv(cb, id_call, 1, &ary); if (rb_obj_is_kind_of(ret_obj, cSSLContext)) { SSL_CTX *ctx2; ossl_sslctx_setup(ret_obj); GetSSLCTX(ret_obj, ctx2); if (!SSL_set_SSL_CTX(ssl, ctx2)) ossl_raise(eSSLError, "SSL_set_SSL_CTX"); 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 Qnil; } static int ssl_servername_cb(SSL *ssl, int *ad, void *arg) { int state; rb_protect(ossl_call_servername_cb, (VALUE)ssl, &state); if (state) { VALUE ssl_obj = (VALUE)SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx); rb_ivar_set(ssl_obj, ID_callback_state, INT2NUM(state)); return SSL_TLSEXT_ERR_ALERT_FATAL; } return SSL_TLSEXT_ERR_OK; } static void ssl_renegotiation_cb(const SSL *ssl) { VALUE ssl_obj, sslctx_obj, cb; ssl_obj = (VALUE)SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx); 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; rb_funcallv(cb, id_call, 1, &ssl_obj); } static VALUE ssl_npn_encode_protocol_i(RB_BLOCK_CALL_FUNC_ARGLIST(cur, 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_block_call(protocols, id_each, 0, 0, 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_funcallv(args->cb, id_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; } #ifdef OSSL_USE_NEXTPROTONEG static int ssl_npn_advertise_cb(SSL *ssl, const unsigned char **out, unsigned int *outlen, void *arg) { VALUE protocols = rb_attr_get((VALUE)arg, id_npn_protocols_encoded); *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 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); } /* 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); } } /* * call-seq: * ctx.options -> integer * * Gets various \OpenSSL options. */ static VALUE ossl_sslctx_get_options(VALUE self) { SSL_CTX *ctx; GetSSLCTX(self, ctx); /* * Do explicit cast because SSL_CTX_get_options() returned (signed) long in * OpenSSL before 1.1.0. */ return ULONG2NUM((unsigned long)SSL_CTX_get_options(ctx)); } /* * call-seq: * ctx.options = integer * * Sets various \OpenSSL options. The options are a bit field and can be * combined with the bitwise OR operator (|). Available options are * defined as constants in OpenSSL::SSL that begin with +OP_+. * * For backwards compatibility, passing +nil+ has the same effect as passing * OpenSSL::SSL::OP_ALL. * * See also man page SSL_CTX_set_options(3). */ 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, NUM2ULONG(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) if (!NIL_P(rb_attr_get(self, id_i_tmp_dh_callback))) { SSL_CTX_set_tmp_dh_callback(ctx, ossl_tmp_dh_callback); SSL_CTX_set_dh_auto(ctx, 0); } #endif #if !defined(OPENSSL_IS_AWSLC) /* AWS-LC has no support for TLS 1.3 PHA. */ SSL_CTX_set_post_handshake_auth(ctx, 1); #endif 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); X509_STORE_up_ref(store); } 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); #ifdef HAVE_SSL_CTX_LOAD_VERIFY_FILE if (ca_file && !SSL_CTX_load_verify_file(ctx, ca_file)) ossl_raise(eSSLError, "SSL_CTX_load_verify_file"); if (ca_path && !SSL_CTX_load_verify_dir(ctx, ca_path)) ossl_raise(eSSLError, "SSL_CTX_load_verify_dir"); #else if (ca_file || ca_path) { if (!SSL_CTX_load_verify_locations(ctx, ca_file, ca_path)) ossl_raise(eSSLError, "SSL_CTX_load_verify_locations"); } #endif 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 OSSL_USE_NEXTPROTONEG 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 *)self); 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 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"); } 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"); } 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"); } #if !OSSL_IS_LIBRESSL /* * It is only compatible with OpenSSL >= 1.1.1. Even if LibreSSL implements * SSL_CTX_set_keylog_callback() from v3.4.2, it does nothing (see * https://github.com/libressl-portable/openbsd/commit/648d39f0f035835d0653342d139883b9661e9cb6). */ if (RTEST(rb_attr_get(self, id_i_keylog_cb))) { SSL_CTX_set_keylog_callback(ctx, ossl_sslctx_keylog_cb); OSSL_Debug("SSL keylog callback added"); } #endif return Qtrue; } static int parse_proto_version(VALUE str) { int i; static const struct { const char *name; int version; } map[] = { { "SSL2", SSL2_VERSION }, { "SSL3", SSL3_VERSION }, { "TLS1", TLS1_VERSION }, { "TLS1_1", TLS1_1_VERSION }, { "TLS1_2", TLS1_2_VERSION }, { "TLS1_3", TLS1_3_VERSION }, }; if (NIL_P(str)) return 0; if (RB_INTEGER_TYPE_P(str)) return NUM2INT(str); if (SYMBOL_P(str)) str = rb_sym2str(str); StringValue(str); for (i = 0; i < numberof(map); i++) if (!strncmp(map[i].name, RSTRING_PTR(str), RSTRING_LEN(str))) return map[i].version; rb_raise(rb_eArgError, "unrecognized version %+"PRIsVALUE, str); } /* * call-seq: * ctx.min_version = OpenSSL::SSL::TLS1_2_VERSION * ctx.min_version = :TLS1_2 * ctx.min_version = nil * * Sets the lower bound on the supported SSL/TLS protocol version. The * version may be specified by an integer constant named * OpenSSL::SSL::*_VERSION, a Symbol, or +nil+ which means "any version". * * === Example * ctx = OpenSSL::SSL::SSLContext.new * ctx.min_version = OpenSSL::SSL::TLS1_1_VERSION * ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION * * sock = OpenSSL::SSL::SSLSocket.new(tcp_sock, ctx) * sock.connect # Initiates a connection using either TLS 1.1 or TLS 1.2 */ static VALUE ossl_sslctx_set_min_version(VALUE self, VALUE v) { SSL_CTX *ctx; int version; rb_check_frozen(self); GetSSLCTX(self, ctx); version = parse_proto_version(v); if (!SSL_CTX_set_min_proto_version(ctx, version)) ossl_raise(eSSLError, "SSL_CTX_set_min_proto_version"); return v; } /* * call-seq: * ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION * ctx.max_version = :TLS1_2 * ctx.max_version = nil * * Sets the upper bound of the supported SSL/TLS protocol version. See * #min_version= for the possible values. */ static VALUE ossl_sslctx_set_max_version(VALUE self, VALUE v) { SSL_CTX *ctx; int version; rb_check_frozen(self); GetSSLCTX(self, ctx); version = parse_proto_version(v); if (!SSL_CTX_set_max_proto_version(ctx, version)) ossl_raise(eSSLError, "SSL_CTX_set_max_proto_version"); return v; } 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); 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; } static VALUE build_cipher_string(VALUE v) { VALUE str, elem; if (RB_TYPE_P(v, T_ARRAY)) { str = rb_str_new(0, 0); for (long 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); } return str; } /* * call-seq: * ctx.ciphers = "cipher1:cipher2:..." * ctx.ciphers = [name, ...] * ctx.ciphers = [[name, version, bits, alg_bits], ...] * * Sets the list of available cipher suites for TLS 1.2 and below 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. * * This method does not affect TLS 1.3 connections. See also #ciphersuites=. */ static VALUE ossl_sslctx_set_ciphers(VALUE self, VALUE v) { SSL_CTX *ctx; VALUE str; rb_check_frozen(self); // Assigning nil is a no-op for compatibility if (NIL_P(v)) return v; str = build_cipher_string(v); GetSSLCTX(self, ctx); if (!SSL_CTX_set_cipher_list(ctx, StringValueCStr(str))) ossl_raise(eSSLError, "SSL_CTX_set_cipher_list"); return v; } /* * call-seq: * ctx.ciphersuites = "cipher1:cipher2:..." * ctx.ciphersuites = [name, ...] * * Sets the list of available TLS 1.3 cipher suites for this context. */ static VALUE ossl_sslctx_set_ciphersuites(VALUE self, VALUE v) { SSL_CTX *ctx; VALUE str; rb_check_frozen(self); // Assigning nil is a no-op for compatibility if (NIL_P(v)) return v; str = build_cipher_string(v); GetSSLCTX(self, ctx); if (!SSL_CTX_set_ciphersuites(ctx, StringValueCStr(str))) ossl_raise(eSSLError, "SSL_CTX_set_ciphersuites"); return v; } #ifdef HAVE_SSL_CTX_SET1_SIGALGS_LIST /* * call-seq: * ctx.sigalgs = "sigalg1:sigalg2:..." * * Sets the list of "supported signature algorithms" for this context. * * For a TLS client, the list is used in the "signature_algorithms" extension * in the ClientHello message. For a server, the list is used by OpenSSL to * determine the set of shared signature algorithms. OpenSSL will pick the most * appropriate one from it. * * See also #client_sigalgs= for the client authentication equivalent. */ static VALUE ossl_sslctx_set_sigalgs(VALUE self, VALUE v) { SSL_CTX *ctx; rb_check_frozen(self); GetSSLCTX(self, ctx); if (!SSL_CTX_set1_sigalgs_list(ctx, StringValueCStr(v))) ossl_raise(eSSLError, "SSL_CTX_set1_sigalgs_list"); return v; } #endif #ifdef HAVE_SSL_CTX_SET1_CLIENT_SIGALGS_LIST /* * call-seq: * ctx.client_sigalgs = "sigalg1:sigalg2:..." * * Sets the list of "supported signature algorithms" for client authentication * for this context. * * For a TLS server, the list is sent to the client as part of the * CertificateRequest message. * * See also #sigalgs= for the server authentication equivalent. */ static VALUE ossl_sslctx_set_client_sigalgs(VALUE self, VALUE v) { SSL_CTX *ctx; rb_check_frozen(self); GetSSLCTX(self, ctx); if (!SSL_CTX_set1_client_sigalgs_list(ctx, StringValueCStr(v))) ossl_raise(eSSLError, "SSL_CTX_set1_client_sigalgs_list"); return v; } #endif #ifndef OPENSSL_NO_DH /* * call-seq: * ctx.tmp_dh = pkey * * Sets DH parameters used for ephemeral DH key exchange. This is relevant for * servers only. * * +pkey+ is an instance of OpenSSL::PKey::DH. Note that key components * contained in the key object, if any, are ignored. The server will always * generate a new key pair for each handshake. * * Added in version 3.0. See also the man page SSL_CTX_set0_tmp_dh_pkey(3). * * Example: * ctx = OpenSSL::SSL::SSLContext.new * ctx.tmp_dh = OpenSSL::DH.generate(2048) * svr = OpenSSL::SSL::SSLServer.new(tcp_svr, ctx) * Thread.new { svr.accept } */ static VALUE ossl_sslctx_set_tmp_dh(VALUE self, VALUE arg) { SSL_CTX *ctx; EVP_PKEY *pkey; rb_check_frozen(self); GetSSLCTX(self, ctx); pkey = GetPKeyPtr(arg); if (EVP_PKEY_base_id(pkey) != EVP_PKEY_DH) rb_raise(eSSLError, "invalid pkey type %s (expected DH)", OBJ_nid2sn(EVP_PKEY_base_id(pkey))); #ifdef HAVE_SSL_CTX_SET0_TMP_DH_PKEY if (!SSL_CTX_set0_tmp_dh_pkey(ctx, pkey)) ossl_raise(eSSLError, "SSL_CTX_set0_tmp_dh_pkey"); EVP_PKEY_up_ref(pkey); #else if (!SSL_CTX_set_tmp_dh(ctx, EVP_PKEY_get0_DH(pkey))) ossl_raise(eSSLError, "SSL_CTX_set_tmp_dh"); #endif // Turn off the "auto" DH parameters set by ossl_sslctx_s_alloc() SSL_CTX_set_dh_auto(ctx, 0); return arg; } #endif /* * call-seq: * ctx.groups = groups_list * ctx.ecdh_curves = groups_list * * Sets the list of supported groups for key agreement for this context. * * For a TLS client, the list is directly used in the "supported_groups" * extension. For a server, the list is used by OpenSSL to determine the set of * shared supported groups. OpenSSL will pick the most appropriate one from it. * * #ecdh_curves= is a deprecated alias for #groups=. * * See also the man page SSL_CTX_set1_groups_list(3). * * === Example * ctx1 = OpenSSL::SSL::SSLContext.new * ctx1.groups = "X25519:P-256:P-224" * svr = OpenSSL::SSL::SSLServer.new(tcp_svr, ctx1) * Thread.new { svr.accept } * * ctx2 = OpenSSL::SSL::SSLContext.new * ctx2.groups = "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_groups(VALUE self, VALUE arg) { SSL_CTX *ctx; rb_check_frozen(self); GetSSLCTX(self, ctx); StringValueCStr(arg); if (!SSL_CTX_set1_groups_list(ctx, RSTRING_PTR(arg))) ossl_raise(eSSLError, "SSL_CTX_set1_groups_list"); return arg; } /* * 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); return INT2NUM(SSL_CTX_get_security_level(ctx)); } /* * 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); SSL_CTX_set_security_level(ctx, NUM2INT(value)); return value; } #ifdef SSL_MODE_SEND_FALLBACK_SCSV /* * call-seq: * ctx.enable_fallback_scsv() => nil * * Activate TLS_FALLBACK_SCSV for this context. * See RFC 7507. */ static VALUE ossl_sslctx_enable_fallback_scsv(VALUE self) { SSL_CTX *ctx; GetSSLCTX(self, ctx); SSL_CTX_set_mode(ctx, SSL_MODE_SEND_FALLBACK_SCSV); return Qnil; } #endif /* * call-seq: * ctx.add_certificate(certificate, pkey [, extra_certs]) -> self * * Adds a certificate to the context. _pkey_ must be a corresponding private * key with _certificate_. * * Multiple certificates with different public key type can be added by * repeated calls of this method, and OpenSSL will choose the most appropriate * certificate during the handshake. * * #cert=, #key=, and #extra_chain_cert= are old accessor methods for setting * certificate and internally call this method. * * === Parameters * _certificate_:: * A certificate. An instance of OpenSSL::X509::Certificate. * _pkey_:: * The private key for _certificate_. An instance of OpenSSL::PKey::PKey. * _extra_certs_:: * Optional. An array of OpenSSL::X509::Certificate. When sending a * certificate chain, the certificates specified by this are sent following * _certificate_, in the order in the array. * * === Example * rsa_cert = OpenSSL::X509::Certificate.new(...) * rsa_pkey = OpenSSL::PKey.read(...) * ca_intermediate_cert = OpenSSL::X509::Certificate.new(...) * ctx.add_certificate(rsa_cert, rsa_pkey, [ca_intermediate_cert]) * * ecdsa_cert = ... * ecdsa_pkey = ... * another_ca_cert = ... * ctx.add_certificate(ecdsa_cert, ecdsa_pkey, [another_ca_cert]) */ static VALUE ossl_sslctx_add_certificate(int argc, VALUE *argv, VALUE self) { VALUE cert, key, extra_chain_ary; SSL_CTX *ctx; X509 *x509; STACK_OF(X509) *extra_chain = NULL; EVP_PKEY *pkey, *pub_pkey; GetSSLCTX(self, ctx); rb_scan_args(argc, argv, "21", &cert, &key, &extra_chain_ary); rb_check_frozen(self); x509 = GetX509CertPtr(cert); pkey = GetPrivPKeyPtr(key); /* * The reference counter is bumped, and decremented immediately. * X509_get0_pubkey() is only available in OpenSSL >= 1.1.0. */ pub_pkey = X509_get_pubkey(x509); EVP_PKEY_free(pub_pkey); if (!pub_pkey) rb_raise(rb_eArgError, "certificate does not contain public key"); if (EVP_PKEY_eq(pub_pkey, pkey) != 1) rb_raise(rb_eArgError, "public key mismatch"); if (argc >= 3) extra_chain = ossl_x509_ary2sk(extra_chain_ary); if (!SSL_CTX_use_certificate(ctx, x509)) { sk_X509_pop_free(extra_chain, X509_free); ossl_raise(eSSLError, "SSL_CTX_use_certificate"); } if (!SSL_CTX_use_PrivateKey(ctx, pkey)) { sk_X509_pop_free(extra_chain, X509_free); ossl_raise(eSSLError, "SSL_CTX_use_PrivateKey"); } if (extra_chain && !SSL_CTX_set0_chain(ctx, extra_chain)) { sk_X509_pop_free(extra_chain, X509_free); ossl_raise(eSSLError, "SSL_CTX_set0_chain"); } return self; } /* * 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); GetSSLSession(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); GetSSLSession(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) -> 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 */ static inline int ssl_started(SSL *ssl) { /* BIO is created through ossl_ssl_setup(), called by #connect or #accept */ return SSL_get_rbio(ssl) != NULL; } static void ossl_ssl_mark(void *ptr) { SSL *ssl = ptr; rb_gc_mark((VALUE)SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx)); } static void ossl_ssl_free(void *ssl) { SSL_free(ssl); } const rb_data_type_t ossl_ssl_type = { "OpenSSL/SSL", { ossl_ssl_mark, ossl_ssl_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; static VALUE ossl_ssl_s_alloc(VALUE klass) { return TypedData_Wrap_Struct(klass, &ossl_ssl_type, NULL); } static VALUE peer_ip_address(VALUE io) { VALUE remote_address = rb_funcall(io, rb_intern("remote_address"), 0); return rb_funcall(remote_address, rb_intern("inspect_sockaddr"), 0); } static VALUE fallback_peer_ip_address(VALUE self, VALUE exc) { return rb_str_new_cstr("(null)"); } static VALUE peeraddr_ip_str(VALUE io) { return rb_rescue2(peer_ip_address, io, fallback_peer_ip_address, Qnil, rb_eSystemCallError, (VALUE)0); } /* * call-seq: * SSLSocket.new(io) => aSSLSocket * SSLSocket.new(io, ctx) => aSSLSocket * SSLSocket.new(io, ctx, sync_close:) => 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 optional _sync_close_ keyword parameter sets the _sync_close_ instance * variable. Setting this to +true+ will cause the underlying socket to be * closed when the SSL/TLS connection is shut down. * * 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) { static ID kw_ids[1]; VALUE kw_args[1]; VALUE opts; VALUE io, v_ctx; 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, &opts) == 1) v_ctx = rb_funcall(cSSLContext, rb_intern("new"), 0); if (!kw_ids[0]) { kw_ids[0] = rb_intern_const("sync_close"); } rb_get_kwargs(opts, kw_ids, 0, 1, kw_args); if (kw_args[0] != Qundef) { rb_ivar_set(self, id_i_sync_close, kw_args[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); Check_Type(io, T_FILE); rb_ivar_set(self, id_i_io, io); ssl = SSL_new(ctx); if (!ssl) ossl_raise(eSSLError, NULL); RTYPEDDATA_DATA(self) = ssl; if (!SSL_set_ex_data(ssl, ossl_ssl_ex_ptr_idx, (void *)self)) ossl_raise(eSSLError, "SSL_set_ex_data"); SSL_set_info_callback(ssl, ssl_info_cb); rb_call_super(0, NULL); return self; } #ifndef HAVE_RB_IO_DESCRIPTOR static int io_descriptor_fallback(VALUE io) { rb_io_t *fptr; GetOpenFile(io, fptr); return fptr->fd; } #define rb_io_descriptor io_descriptor_fallback #endif 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(rb_io_descriptor(io)))) ossl_raise(eSSLError, "SSL_set_fd"); return Qtrue; } static int errno_mapped(void) { #ifdef _WIN32 return rb_w32_map_errno(WSAGetLastError()); #else return errno; #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; } // Provided by Ruby 3.2.0 and later in order to support the default IO#timeout. #ifndef RUBY_IO_TIMEOUT_DEFAULT #define RUBY_IO_TIMEOUT_DEFAULT Qnil #endif #ifdef HAVE_RB_IO_TIMEOUT #define IO_TIMEOUT_ERROR rb_eIOTimeoutError #else #define IO_TIMEOUT_ERROR rb_eIOError #endif static void io_wait_writable(VALUE io) { #ifdef HAVE_RB_IO_MAYBE_WAIT if (!rb_io_wait(io, INT2NUM(RUBY_IO_WRITABLE), RUBY_IO_TIMEOUT_DEFAULT)) { rb_raise(IO_TIMEOUT_ERROR, "Timed out while waiting to become writable!"); } #else rb_io_t *fptr; GetOpenFile(io, fptr); rb_thread_fd_writable(fptr->fd); #endif } static void io_wait_readable(VALUE io) { #ifdef HAVE_RB_IO_MAYBE_WAIT if (!rb_io_wait(io, INT2NUM(RUBY_IO_READABLE), RUBY_IO_TIMEOUT_DEFAULT)) { rb_raise(IO_TIMEOUT_ERROR, "Timed out while waiting to become readable!"); } #else rb_io_t *fptr; GetOpenFile(io, fptr); rb_thread_wait_fd(fptr->fd); #endif } static VALUE ossl_start_ssl(VALUE self, int (*func)(SSL *), const char *funcname, VALUE opts) { SSL *ssl; VALUE cb_state; int nonblock = opts != Qfalse; rb_ivar_set(self, ID_callback_state, Qnil); GetSSL(self, ssl); VALUE io = rb_attr_get(self, id_i_io); for (;;) { int ret = func(ssl); int saved_errno = errno_mapped(); 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; int code = SSL_get_error(ssl, ret); switch (code) { case SSL_ERROR_WANT_WRITE: if (no_exception_p(opts)) { return sym_wait_writable; } write_would_block(nonblock); io_wait_writable(io); continue; case SSL_ERROR_WANT_READ: if (no_exception_p(opts)) { return sym_wait_readable; } read_would_block(nonblock); io_wait_readable(io); continue; case SSL_ERROR_SYSCALL: #ifdef __APPLE__ /* See ossl_ssl_write_internal() */ if (saved_errno == EPROTOTYPE) continue; #endif if (saved_errno) rb_exc_raise(rb_syserr_new(saved_errno, funcname)); /* fallthrough */ default: { VALUE error_append = Qnil; #if defined(SSL_R_CERTIFICATE_VERIFY_FAILED) unsigned long err = ERR_peek_last_error(); if (ERR_GET_LIB(err) == ERR_LIB_SSL && ERR_GET_REASON(err) == SSL_R_CERTIFICATE_VERIFY_FAILED) { const char *err_msg = ERR_reason_error_string(err), *verify_msg = X509_verify_cert_error_string(SSL_get_verify_result(ssl)); if (!err_msg) err_msg = "(null)"; if (!verify_msg) verify_msg = "(null)"; ossl_clear_error(); /* let ossl_raise() not append message */ error_append = rb_sprintf(": %s (%s)", err_msg, verify_msg); } #endif ossl_raise(eSSLError, "%s%s returned=%d errno=%d peeraddr=%"PRIsVALUE" state=%s%"PRIsVALUE, funcname, code == SSL_ERROR_SYSCALL ? " SYSCALL" : "", code, saved_errno, peeraddr_ip_str(io), SSL_state_string_long(ssl), error_append); } } } return self; } /* * call-seq: * ssl.connect => self * * Initiates an SSL/TLS handshake with a server. */ 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 a keyword argument _exception_ to +false+, you can 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. */ 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 a keyword argument _exception_ to +false+, you can 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; VALUE len, str, cb_state; VALUE opts = Qnil; if (nonblock) { rb_scan_args(argc, argv, "11:", &len, &str, &opts); } else { rb_scan_args(argc, argv, "11", &len, &str); } GetSSL(self, ssl); if (!ssl_started(ssl)) rb_raise(eSSLError, "SSL session is not started yet"); ilen = NUM2INT(len); if (NIL_P(str)) str = rb_str_new(0, ilen); else { StringValue(str); if (RSTRING_LEN(str) >= ilen) rb_str_modify(str); else rb_str_modify_expand(str, ilen - RSTRING_LEN(str)); } if (ilen == 0) { rb_str_set_len(str, 0); return str; } VALUE io = rb_attr_get(self, id_i_io); for (;;) { rb_str_locktmp(str); int nread = SSL_read(ssl, RSTRING_PTR(str), ilen); int saved_errno = errno_mapped(); rb_str_unlocktmp(str); cb_state = rb_attr_get(self, ID_callback_state); if (!NIL_P(cb_state)) { rb_ivar_set(self, ID_callback_state, Qnil); ossl_clear_error(); rb_jump_tag(NUM2INT(cb_state)); } switch (SSL_get_error(ssl, nread)) { case SSL_ERROR_NONE: rb_str_set_len(str, nread); return str; case SSL_ERROR_ZERO_RETURN: if (no_exception_p(opts)) { return Qnil; } rb_eof_error(); case SSL_ERROR_WANT_WRITE: if (nonblock) { if (no_exception_p(opts)) { return sym_wait_writable; } write_would_block(nonblock); } io_wait_writable(io); break; case SSL_ERROR_WANT_READ: if (nonblock) { if (no_exception_p(opts)) { return sym_wait_readable; } read_would_block(nonblock); } io_wait_readable(io); break; case SSL_ERROR_SYSCALL: if (!ERR_peek_error()) { if (saved_errno) rb_exc_raise(rb_syserr_new(saved_errno, "SSL_read")); 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(); } } /* fall through */ default: ossl_raise(eSSLError, "SSL_read"); } // Ensure the buffer is not modified during io_wait_*able() rb_str_modify(str); if (rb_str_capacity(str) < (size_t)ilen) rb_raise(eSSLError, "read buffer was modified"); } } /* * 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_safe(VALUE _args) { VALUE *args = (VALUE*)_args; VALUE self = args[0]; VALUE str = args[1]; VALUE opts = args[2]; SSL *ssl; rb_io_t *fptr; int num, nonblock = opts != Qfalse; VALUE cb_state; GetSSL(self, ssl); if (!ssl_started(ssl)) rb_raise(eSSLError, "SSL session is not started yet"); VALUE io = rb_attr_get(self, id_i_io); GetOpenFile(io, fptr); /* SSL_write(3ssl) manpage states num == 0 is undefined */ num = RSTRING_LENINT(str); if (num == 0) return INT2FIX(0); for (;;) { int nwritten = SSL_write(ssl, RSTRING_PTR(str), num); int saved_errno = errno_mapped(); cb_state = rb_attr_get(self, ID_callback_state); if (!NIL_P(cb_state)) { rb_ivar_set(self, ID_callback_state, Qnil); ossl_clear_error(); rb_jump_tag(NUM2INT(cb_state)); } switch (SSL_get_error(ssl, nwritten)) { case SSL_ERROR_NONE: return INT2NUM(nwritten); case SSL_ERROR_WANT_WRITE: if (no_exception_p(opts)) { return sym_wait_writable; } write_would_block(nonblock); io_wait_writable(io); continue; case SSL_ERROR_WANT_READ: if (no_exception_p(opts)) { return sym_wait_readable; } read_would_block(nonblock); io_wait_readable(io); continue; case SSL_ERROR_SYSCALL: #ifdef __APPLE__ /* * It appears that send syscall can return EPROTOTYPE if the * socket is being torn down. Retry to get a proper errno to * make the error handling in line with the socket library. * [Bug #14713] https://bugs.ruby-lang.org/issues/14713 */ if (saved_errno == EPROTOTYPE) continue; #endif if (saved_errno) rb_exc_raise(rb_syserr_new(saved_errno, "SSL_write")); /* fallthrough */ default: ossl_raise(eSSLError, "SSL_write"); } } } static VALUE ossl_ssl_write_internal(VALUE self, VALUE str, VALUE opts) { StringValue(str); int frozen = RB_OBJ_FROZEN(str); if (!frozen) { rb_str_locktmp(str); } int state; VALUE args[3] = {self, str, opts}; VALUE result = rb_protect(ossl_ssl_write_internal_safe, (VALUE)args, &state); if (!frozen) { rb_str_unlocktmp(str); } if (state) { rb_jump_tag(state); } return result; } /* * 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 -> nil or [name, version, bits, alg_bits] * * Returns the cipher suite actually used in the current session, or nil if * no session has been established. */ static VALUE ossl_ssl_get_cipher(VALUE self) { SSL *ssl; const SSL_CIPHER *cipher; GetSSL(self, ssl); cipher = SSL_get_current_cipher(ssl); return cipher ? ossl_ssl_cipher_to_ary(cipher) : Qnil; } /* * 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); GetSSLSession(arg1, sess); if (SSL_set_session(ssl, sess) != 1) ossl_raise(eSSLError, "SSL_set_session"); return arg1; } /* * 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; } /* * 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 LONG2NUM(SSL_get_verify_result(ssl)); } /* * call-seq: * ssl.finished_message => "finished message" * * Returns the last *Finished* message sent * */ static VALUE ossl_ssl_get_finished(VALUE self) { SSL *ssl; char sizer[1], *buf; size_t len; GetSSL(self, ssl); len = SSL_get_finished(ssl, sizer, 0); if (len == 0) return Qnil; buf = ALLOCA_N(char, len); SSL_get_finished(ssl, buf, len); return rb_str_new(buf, len); } /* * call-seq: * ssl.peer_finished_message => "peer finished message" * * Returns the last *Finished* message received * */ static VALUE ossl_ssl_get_peer_finished(VALUE self) { SSL *ssl; char sizer[1], *buf; size_t len; GetSSL(self, ssl); len = SSL_get_peer_finished(ssl, sizer, 0); if (len == 0) return Qnil; buf = ALLOCA_N(char, len); SSL_get_peer_finished(ssl, buf, len); return rb_str_new(buf, len); } /* * call-seq: * ssl.client_ca => [x509name, ...] or nil * * 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); if (!ca) return Qnil; return ossl_x509name_sk2ary(ca); } # ifdef OSSL_USE_NEXTPROTONEG /* * 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 /* * 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); } /* * call-seq: * session.export_keying_material(label, length) -> String * * Enables use of shared session key material in accordance with RFC 5705. */ static VALUE ossl_ssl_export_keying_material(int argc, VALUE *argv, VALUE self) { SSL *ssl; VALUE str; VALUE label; VALUE length; VALUE context; unsigned char *p; size_t len; int use_ctx = 0; unsigned char *ctx = NULL; size_t ctx_len = 0; int ret; rb_scan_args(argc, argv, "21", &label, &length, &context); StringValue(label); GetSSL(self, ssl); len = (size_t)NUM2LONG(length); str = rb_str_new(0, len); p = (unsigned char *)RSTRING_PTR(str); if (!NIL_P(context)) { use_ctx = 1; StringValue(context); ctx = (unsigned char *)RSTRING_PTR(context); ctx_len = RSTRING_LEN(context); } ret = SSL_export_keying_material(ssl, p, len, (char *)RSTRING_PTR(label), RSTRING_LENINT(label), ctx, ctx_len, use_ctx); if (ret == 0 || ret == -1) { ossl_raise(eSSLError, "SSL_export_keying_material"); } return str; } /* * 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_wrap(key); } #ifdef HAVE_SSL_GET0_PEER_SIGNATURE_NAME /* * call-seq: * ssl.sigalg => String or nil * * Returns the signature algorithm name, the IANA name of the signature scheme * used by the local to sign the TLS handshake. */ static VALUE ossl_ssl_get_sigalg(VALUE self) { SSL *ssl; const char *name; GetSSL(self, ssl); if (!SSL_get0_signature_name(ssl, &name)) return Qnil; return rb_str_new_cstr(name); } /* * call-seq: * ssl.peer_sigalg => String or nil * * Returns the signature algorithm name, the IANA name of the signature scheme * used by the peer to sign the TLS handshake. */ static VALUE ossl_ssl_get_peer_sigalg(VALUE self) { SSL *ssl; const char *name; GetSSL(self, ssl); if (!SSL_get0_peer_signature_name(ssl, &name)) return Qnil; return rb_str_new_cstr(name); } #endif #ifdef HAVE_SSL_GET0_GROUP_NAME /* * call-seq: * ssl.group => String or nil * * Returns the name of the group that was used for the key agreement of the * current TLS session establishment. */ static VALUE ossl_ssl_get_group(VALUE self) { SSL *ssl; const char *name; GetSSL(self, ssl); if (!(name = SSL_get0_group_name(ssl))) return Qnil; return rb_str_new_cstr(name); } #endif #endif /* !defined(OPENSSL_NO_SOCK) */ void Init_ossl_ssl(void) { #if 0 rb_mWaitReadable = rb_define_module_under(rb_cIO, "WaitReadable"); rb_mWaitWritable = rb_define_module_under(rb_cIO, "WaitWritable"); #endif #ifndef OPENSSL_NO_SOCK id_call = rb_intern_const("call"); ID_callback_state = rb_intern_const("callback_state"); ossl_ssl_ex_ptr_idx = SSL_get_ex_new_index(0, (void *)"ossl_ssl_ex_ptr_idx", 0, 0, 0); if (ossl_ssl_ex_ptr_idx < 0) ossl_raise(rb_eRuntimeError, "SSL_get_ex_new_index"); ossl_sslctx_ex_ptr_idx = SSL_CTX_get_ex_new_index(0, (void *)"ossl_sslctx_ex_ptr_idx", 0, 0, 0); if (ossl_sslctx_ex_ptr_idx < 0) ossl_raise(rb_eRuntimeError, "SSL_CTX_get_ex_new_index"); /* 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-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 * * The _cert_, _key_, and _extra_chain_cert_ attributes are deprecated. * It is recommended to use #add_certificate instead. */ rb_attr(cSSLContext, rb_intern_const("cert"), 1, 1, Qfalse); /* * Context private key * * The _cert_, _key_, and _extra_chain_cert_ attributes are deprecated. * It is recommended to use #add_certificate instead. */ rb_attr(cSSLContext, rb_intern_const("key"), 1, 1, Qfalse); /* * A certificate or Array of certificates that will be sent to the client. */ rb_attr(cSSLContext, rb_intern_const("client_ca"), 1, 1, Qfalse); /* * The path to a file containing a PEM-format CA certificate */ rb_attr(cSSLContext, rb_intern_const("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_const("ca_path"), 1, 1, Qfalse); /* * Maximum session lifetime in seconds. */ rb_attr(cSSLContext, rb_intern_const("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_const("verify_mode"), 1, 1, Qfalse); /* * Number of CA certificates to walk when verifying a certificate chain. */ rb_attr(cSSLContext, rb_intern_const("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_const("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_const("verify_hostname"), 1, 1, Qfalse); /* * An OpenSSL::X509::Store used for certificate verification. */ rb_attr(cSSLContext, rb_intern_const("cert_store"), 1, 1, Qfalse); /* * An Array of extra X509 certificates to be added to the certificate * chain. * * The _cert_, _key_, and _extra_chain_cert_ attributes are deprecated. * It is recommended to use #add_certificate instead. */ rb_attr(cSSLContext, rb_intern_const("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_const("client_cert_cb"), 1, 1, Qfalse); #ifndef OPENSSL_NO_DH /* * A callback invoked when DH parameters are required for ephemeral DH key * exchange. * * The callback is invoked with the SSLSocket, a * 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. * * Deprecated in version 3.0. Use #tmp_dh= instead. */ rb_attr(cSSLContext, rb_intern_const("tmp_dh_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_const("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_const("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_const("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. * * IMPORTANT NOTE: It is currently not possible to use this safely in a * multi-threaded application. The callback is called inside a global lock * and it can randomly cause deadlock on Ruby thread switching. */ rb_attr(cSSLContext, rb_intern_const("session_remove_cb"), 1, 1, Qfalse); /* * A callback invoked whenever a new handshake is initiated on an * established connection. May be used to disable renegotiation entirely. * * The callback is invoked with the active SSLSocket. The callback's * return value is ignored. A normal return indicates "approval" of the * renegotiation and will continue the process. To forbid renegotiation * and to cancel the process, raise an exception 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: * * ctx.renegotiation_cb = lambda do |ssl| * raise RuntimeError, "Client renegotiation disabled" * end */ rb_attr(cSSLContext, rb_intern_const("renegotiation_cb"), 1, 1, Qfalse); #ifdef OSSL_USE_NEXTPROTONEG /* * 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_const("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_const("npn_select_cb"), 1, 1, Qfalse); #endif /* * 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_const("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_const("alpn_select_cb"), 1, 1, Qfalse); /* * A callback invoked when TLS key material is generated or received, in * order to allow applications to store this keying material for debugging * purposes. * * The callback is invoked with an SSLSocket and a string containing the * key material in the format used by NSS for its SSLKEYLOGFILE debugging * output. * * It is only compatible with OpenSSL >= 1.1.1. Even if LibreSSL implements * SSL_CTX_set_keylog_callback() from v3.4.2, it does nothing (see * https://github.com/libressl-portable/openbsd/commit/648d39f0f035835d0653342d139883b9661e9cb6). * * === Example * * context.keylog_cb = proc do |_sock, line| * File.open('ssl_keylog_file', "a") do |f| * f.write("#{line}\n") * end * end */ rb_attr(cSSLContext, rb_intern_const("keylog_cb"), 1, 1, Qfalse); rb_define_alias(cSSLContext, "ssl_timeout", "timeout"); rb_define_alias(cSSLContext, "ssl_timeout=", "timeout="); rb_define_method(cSSLContext, "min_version=", ossl_sslctx_set_min_version, 1); rb_define_method(cSSLContext, "max_version=", ossl_sslctx_set_max_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, "ciphersuites=", ossl_sslctx_set_ciphersuites, 1); #ifdef HAVE_SSL_CTX_SET1_SIGALGS_LIST // Not in LibreSSL yet rb_define_method(cSSLContext, "sigalgs=", ossl_sslctx_set_sigalgs, 1); #endif #ifdef HAVE_SSL_CTX_SET1_CLIENT_SIGALGS_LIST // Not in LibreSSL or AWS-LC yet rb_define_method(cSSLContext, "client_sigalgs=", ossl_sslctx_set_client_sigalgs, 1); #endif #ifndef OPENSSL_NO_DH rb_define_method(cSSLContext, "tmp_dh=", ossl_sslctx_set_tmp_dh, 1); #endif rb_define_method(cSSLContext, "groups=", ossl_sslctx_set_groups, 1); rb_define_alias(cSSLContext, "ecdh_curves=", "groups="); rb_define_method(cSSLContext, "security_level", ossl_sslctx_get_security_level, 0); rb_define_method(cSSLContext, "security_level=", ossl_sslctx_set_security_level, 1); #ifdef SSL_MODE_SEND_FALLBACK_SCSV rb_define_method(cSSLContext, "enable_fallback_scsv", ossl_sslctx_enable_fallback_scsv, 0); #endif rb_define_method(cSSLContext, "add_certificate", ossl_sslctx_add_certificate, -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); /* * Document-class: OpenSSL::SSL::SSLSocket */ cSSLSocket = rb_define_class_under(mSSL, "SSLSocket", rb_cObject); 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); /* #hostname is defined in lib/openssl/ssl.rb */ rb_define_method(cSSLSocket, "hostname=", ossl_ssl_set_hostname, 1); rb_define_method(cSSLSocket, "finished_message", ossl_ssl_get_finished, 0); rb_define_method(cSSLSocket, "peer_finished_message", ossl_ssl_get_peer_finished, 0); rb_define_method(cSSLSocket, "tmp_key", ossl_ssl_tmp_key, 0); rb_define_method(cSSLSocket, "alpn_protocol", ossl_ssl_alpn_protocol, 0); rb_define_method(cSSLSocket, "export_keying_material", ossl_ssl_export_keying_material, -1); # ifdef OSSL_USE_NEXTPROTONEG rb_define_method(cSSLSocket, "npn_protocol", ossl_ssl_npn_protocol, 0); # endif #ifdef HAVE_SSL_GET0_PEER_SIGNATURE_NAME rb_define_method(cSSLSocket, "sigalg", ossl_ssl_get_sigalg, 0); rb_define_method(cSSLSocket, "peer_sigalg", ossl_ssl_get_peer_sigalg, 0); #endif #ifdef HAVE_SSL_GET0_GROUP_NAME rb_define_method(cSSLSocket, "group", ossl_ssl_get_group, 0); #endif rb_define_const(mSSL, "VERIFY_NONE", INT2NUM(SSL_VERIFY_NONE)); rb_define_const(mSSL, "VERIFY_PEER", INT2NUM(SSL_VERIFY_PEER)); rb_define_const(mSSL, "VERIFY_FAIL_IF_NO_PEER_CERT", INT2NUM(SSL_VERIFY_FAIL_IF_NO_PEER_CERT)); rb_define_const(mSSL, "VERIFY_CLIENT_ONCE", INT2NUM(SSL_VERIFY_CLIENT_ONCE)); rb_define_const(mSSL, "OP_ALL", ULONG2NUM(SSL_OP_ALL)); #ifdef SSL_OP_CLEANSE_PLAINTEXT /* OpenSSL 3.0 */ rb_define_const(mSSL, "OP_CLEANSE_PLAINTEXT", ULONG2NUM(SSL_OP_CLEANSE_PLAINTEXT)); #endif rb_define_const(mSSL, "OP_LEGACY_SERVER_CONNECT", ULONG2NUM(SSL_OP_LEGACY_SERVER_CONNECT)); #ifdef SSL_OP_ENABLE_KTLS /* OpenSSL 3.0 */ rb_define_const(mSSL, "OP_ENABLE_KTLS", ULONG2NUM(SSL_OP_ENABLE_KTLS)); #endif rb_define_const(mSSL, "OP_TLSEXT_PADDING", ULONG2NUM(SSL_OP_TLSEXT_PADDING)); rb_define_const(mSSL, "OP_SAFARI_ECDHE_ECDSA_BUG", ULONG2NUM(SSL_OP_SAFARI_ECDHE_ECDSA_BUG)); #ifdef SSL_OP_IGNORE_UNEXPECTED_EOF /* OpenSSL 3.0 */ rb_define_const(mSSL, "OP_IGNORE_UNEXPECTED_EOF", ULONG2NUM(SSL_OP_IGNORE_UNEXPECTED_EOF)); #endif #ifdef SSL_OP_ALLOW_CLIENT_RENEGOTIATION /* OpenSSL 3.0 */ rb_define_const(mSSL, "OP_ALLOW_CLIENT_RENEGOTIATION", ULONG2NUM(SSL_OP_ALLOW_CLIENT_RENEGOTIATION)); #endif #ifdef SSL_OP_DISABLE_TLSEXT_CA_NAMES /* OpenSSL 3.0 */ rb_define_const(mSSL, "OP_DISABLE_TLSEXT_CA_NAMES", ULONG2NUM(SSL_OP_DISABLE_TLSEXT_CA_NAMES)); #endif #ifdef SSL_OP_ALLOW_NO_DHE_KEX /* OpenSSL 1.1.1, missing in LibreSSL */ rb_define_const(mSSL, "OP_ALLOW_NO_DHE_KEX", ULONG2NUM(SSL_OP_ALLOW_NO_DHE_KEX)); #endif rb_define_const(mSSL, "OP_DONT_INSERT_EMPTY_FRAGMENTS", ULONG2NUM(SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS)); rb_define_const(mSSL, "OP_NO_TICKET", ULONG2NUM(SSL_OP_NO_TICKET)); rb_define_const(mSSL, "OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION", ULONG2NUM(SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION)); rb_define_const(mSSL, "OP_NO_COMPRESSION", ULONG2NUM(SSL_OP_NO_COMPRESSION)); rb_define_const(mSSL, "OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION", ULONG2NUM(SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION)); #ifdef SSL_OP_NO_ENCRYPT_THEN_MAC /* OpenSSL 1.1.1, missing in LibreSSL */ rb_define_const(mSSL, "OP_NO_ENCRYPT_THEN_MAC", ULONG2NUM(SSL_OP_NO_ENCRYPT_THEN_MAC)); #endif #ifdef SSL_OP_ENABLE_MIDDLEBOX_COMPAT /* OpenSSL 1.1.1, missing in LibreSSL */ rb_define_const(mSSL, "OP_ENABLE_MIDDLEBOX_COMPAT", ULONG2NUM(SSL_OP_ENABLE_MIDDLEBOX_COMPAT)); #endif #ifdef SSL_OP_PRIORITIZE_CHACHA /* OpenSSL 1.1.1, missing in LibreSSL */ rb_define_const(mSSL, "OP_PRIORITIZE_CHACHA", ULONG2NUM(SSL_OP_PRIORITIZE_CHACHA)); #endif #ifdef SSL_OP_NO_ANTI_REPLAY /* OpenSSL 1.1.1, missing in LibreSSL */ rb_define_const(mSSL, "OP_NO_ANTI_REPLAY", ULONG2NUM(SSL_OP_NO_ANTI_REPLAY)); #endif rb_define_const(mSSL, "OP_NO_SSLv3", ULONG2NUM(SSL_OP_NO_SSLv3)); rb_define_const(mSSL, "OP_NO_TLSv1", ULONG2NUM(SSL_OP_NO_TLSv1)); rb_define_const(mSSL, "OP_NO_TLSv1_1", ULONG2NUM(SSL_OP_NO_TLSv1_1)); rb_define_const(mSSL, "OP_NO_TLSv1_2", ULONG2NUM(SSL_OP_NO_TLSv1_2)); rb_define_const(mSSL, "OP_NO_TLSv1_3", ULONG2NUM(SSL_OP_NO_TLSv1_3)); rb_define_const(mSSL, "OP_CIPHER_SERVER_PREFERENCE", ULONG2NUM(SSL_OP_CIPHER_SERVER_PREFERENCE)); rb_define_const(mSSL, "OP_TLS_ROLLBACK_BUG", ULONG2NUM(SSL_OP_TLS_ROLLBACK_BUG)); #ifdef SSL_OP_NO_RENEGOTIATION /* OpenSSL 1.1.1, missing in LibreSSL */ rb_define_const(mSSL, "OP_NO_RENEGOTIATION", ULONG2NUM(SSL_OP_NO_RENEGOTIATION)); #endif rb_define_const(mSSL, "OP_CRYPTOPRO_TLSEXT_BUG", ULONG2NUM(SSL_OP_CRYPTOPRO_TLSEXT_BUG)); /* SSL_OP_* flags for DTLS */ #if 0 rb_define_const(mSSL, "OP_NO_QUERY_MTU", ULONG2NUM(SSL_OP_NO_QUERY_MTU)); rb_define_const(mSSL, "OP_COOKIE_EXCHANGE", ULONG2NUM(SSL_OP_COOKIE_EXCHANGE)); rb_define_const(mSSL, "OP_CISCO_ANYCONNECT", ULONG2NUM(SSL_OP_CISCO_ANYCONNECT)); #endif /* Deprecated in OpenSSL 1.1.0. */ rb_define_const(mSSL, "OP_MICROSOFT_SESS_ID_BUG", ULONG2NUM(SSL_OP_MICROSOFT_SESS_ID_BUG)); /* Deprecated in OpenSSL 1.1.0. */ rb_define_const(mSSL, "OP_NETSCAPE_CHALLENGE_BUG", ULONG2NUM(SSL_OP_NETSCAPE_CHALLENGE_BUG)); /* Deprecated in OpenSSL 0.9.8q and 1.0.0c. */ rb_define_const(mSSL, "OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG", ULONG2NUM(SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG)); /* Deprecated in OpenSSL 1.0.1h and 1.0.2. */ rb_define_const(mSSL, "OP_SSLREF2_REUSE_CERT_TYPE_BUG", ULONG2NUM(SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG)); /* Deprecated in OpenSSL 1.1.0. */ rb_define_const(mSSL, "OP_MICROSOFT_BIG_SSLV3_BUFFER", ULONG2NUM(SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER)); /* Deprecated in OpenSSL 0.9.7h and 0.9.8b. */ rb_define_const(mSSL, "OP_MSIE_SSLV2_RSA_PADDING", ULONG2NUM(SSL_OP_MSIE_SSLV2_RSA_PADDING)); /* Deprecated in OpenSSL 1.1.0. */ rb_define_const(mSSL, "OP_SSLEAY_080_CLIENT_DH_BUG", ULONG2NUM(SSL_OP_SSLEAY_080_CLIENT_DH_BUG)); /* Deprecated in OpenSSL 1.1.0. */ rb_define_const(mSSL, "OP_TLS_D5_BUG", ULONG2NUM(SSL_OP_TLS_D5_BUG)); /* Deprecated in OpenSSL 1.1.0. */ rb_define_const(mSSL, "OP_TLS_BLOCK_PADDING_BUG", ULONG2NUM(SSL_OP_TLS_BLOCK_PADDING_BUG)); /* Deprecated in OpenSSL 1.1.0. */ rb_define_const(mSSL, "OP_SINGLE_ECDH_USE", ULONG2NUM(SSL_OP_SINGLE_ECDH_USE)); /* Deprecated in OpenSSL 1.1.0. */ rb_define_const(mSSL, "OP_SINGLE_DH_USE", ULONG2NUM(SSL_OP_SINGLE_DH_USE)); /* Deprecated in OpenSSL 1.0.1k and 1.0.2. */ rb_define_const(mSSL, "OP_EPHEMERAL_RSA", ULONG2NUM(SSL_OP_EPHEMERAL_RSA)); /* Deprecated in OpenSSL 1.1.0. */ rb_define_const(mSSL, "OP_NO_SSLv2", ULONG2NUM(SSL_OP_NO_SSLv2)); /* Deprecated in OpenSSL 1.0.1. */ rb_define_const(mSSL, "OP_PKCS1_CHECK_1", ULONG2NUM(SSL_OP_PKCS1_CHECK_1)); /* Deprecated in OpenSSL 1.0.1. */ rb_define_const(mSSL, "OP_PKCS1_CHECK_2", ULONG2NUM(SSL_OP_PKCS1_CHECK_2)); /* Deprecated in OpenSSL 1.1.0. */ rb_define_const(mSSL, "OP_NETSCAPE_CA_DN_BUG", ULONG2NUM(SSL_OP_NETSCAPE_CA_DN_BUG)); /* Deprecated in OpenSSL 1.1.0. */ rb_define_const(mSSL, "OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG", ULONG2NUM(SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG)); /* * SSL/TLS version constants. Used by SSLContext#min_version= and * #max_version= */ /* SSL 2.0 */ rb_define_const(mSSL, "SSL2_VERSION", INT2NUM(SSL2_VERSION)); /* SSL 3.0 */ rb_define_const(mSSL, "SSL3_VERSION", INT2NUM(SSL3_VERSION)); /* TLS 1.0 */ rb_define_const(mSSL, "TLS1_VERSION", INT2NUM(TLS1_VERSION)); /* TLS 1.1 */ rb_define_const(mSSL, "TLS1_1_VERSION", INT2NUM(TLS1_1_VERSION)); /* TLS 1.2 */ rb_define_const(mSSL, "TLS1_2_VERSION", INT2NUM(TLS1_2_VERSION)); /* TLS 1.3 */ rb_define_const(mSSL, "TLS1_3_VERSION", INT2NUM(TLS1_3_VERSION)); sym_exception = ID2SYM(rb_intern_const("exception")); sym_wait_readable = ID2SYM(rb_intern_const("wait_readable")); sym_wait_writable = ID2SYM(rb_intern_const("wait_writable")); id_npn_protocols_encoded = rb_intern_const("npn_protocols_encoded"); id_each = rb_intern_const("each"); #define DefIVarID(name) do \ id_i_##name = rb_intern_const("@"#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(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(keylog_cb); DefIVarID(tmp_dh_callback); DefIVarID(io); DefIVarID(context); DefIVarID(hostname); DefIVarID(sync_close); #endif /* !defined(OPENSSL_NO_SOCK) */ } ================================================ FILE: ext/openssl/ossl_ssl.h ================================================ /* * 'OpenSSL for Ruby' project * Copyright (C) 2001-2002 Michal Rokos * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #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) 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_ */ ================================================ FILE: ext/openssl/ossl_ssl_session.c ================================================ /* * Copyright (C) 2004-2007 Technorama Ltd. */ #include "ossl.h" #ifndef OPENSSL_NO_SOCK 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 | RUBY_TYPED_WB_PROTECTED, }; 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; if (RTYPEDDATA_DATA(self)) 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 = d2i_SSL_SESSION_bio(in, NULL); if (!ctx) { OSSL_BIO_reset(in); ctx = PEM_read_bio_SSL_SESSION(in, NULL, NULL, NULL); } BIO_free(in); if (!ctx) ossl_raise(rb_eArgError, "unknown type"); } RTYPEDDATA_DATA(self) = ctx; return self; } /* :nodoc: */ 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 */ GetSSLSession(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; } static 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); } /* * 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); GetSSLSession(val2, ctx2); switch (ossl_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); } #endif /* !defined(OPENSSL_NO_SOCK) */ void Init_ossl_ssl_session(void) { #ifndef OPENSSL_NO_SOCK 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_method(cSSLSession, "initialize_copy", ossl_ssl_session_initialize_copy, 1); 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); #endif /* !defined(OPENSSL_NO_SOCK) */ } ================================================ FILE: ext/openssl/ossl_ts.c ================================================ /* * * Copyright (C) 2010 Martin Bosslet * All rights reserved. */ /* * This program is licenced under the same licence as Ruby. * (See the file 'COPYING'.) */ #include "ossl.h" #ifndef OPENSSL_NO_TS #define NewTSRequest(klass) \ TypedData_Wrap_Struct((klass), &ossl_ts_req_type, 0) #define SetTSRequest(obj, req) do { \ if (!(req)) { \ ossl_raise(rb_eRuntimeError, "TS_REQ wasn't initialized."); \ } \ RTYPEDDATA_DATA(obj) = (req); \ } while (0) #define GetTSRequest(obj, req) do { \ TypedData_Get_Struct((obj), TS_REQ, &ossl_ts_req_type, (req)); \ if (!(req)) { \ ossl_raise(rb_eRuntimeError, "TS_REQ wasn't initialized."); \ } \ } while (0) #define NewTSResponse(klass) \ TypedData_Wrap_Struct((klass), &ossl_ts_resp_type, 0) #define SetTSResponse(obj, resp) do { \ if (!(resp)) { \ ossl_raise(rb_eRuntimeError, "TS_RESP wasn't initialized."); \ } \ RTYPEDDATA_DATA(obj) = (resp); \ } while (0) #define GetTSResponse(obj, resp) do { \ TypedData_Get_Struct((obj), TS_RESP, &ossl_ts_resp_type, (resp)); \ if (!(resp)) { \ ossl_raise(rb_eRuntimeError, "TS_RESP wasn't initialized."); \ } \ } while (0) #define NewTSTokenInfo(klass) \ TypedData_Wrap_Struct((klass), &ossl_ts_token_info_type, 0) #define SetTSTokenInfo(obj, info) do { \ if (!(info)) { \ ossl_raise(rb_eRuntimeError, "TS_TST_INFO wasn't initialized."); \ } \ RTYPEDDATA_DATA(obj) = (info); \ } while (0) #define GetTSTokenInfo(obj, info) do { \ TypedData_Get_Struct((obj), TS_TST_INFO, &ossl_ts_token_info_type, (info)); \ if (!(info)) { \ ossl_raise(rb_eRuntimeError, "TS_TST_INFO wasn't initialized."); \ } \ } while (0) #define ossl_tsfac_get_default_policy_id(o) rb_attr_get((o),rb_intern("@default_policy_id")) #define ossl_tsfac_get_serial_number(o) rb_attr_get((o),rb_intern("@serial_number")) #define ossl_tsfac_get_gen_time(o) rb_attr_get((o),rb_intern("@gen_time")) #define ossl_tsfac_get_additional_certs(o) rb_attr_get((o),rb_intern("@additional_certs")) #define ossl_tsfac_get_allowed_digests(o) rb_attr_get((o),rb_intern("@allowed_digests")) static VALUE mTimestamp; static VALUE eTimestampError; static VALUE cTimestampRequest; static VALUE cTimestampResponse; static VALUE cTimestampTokenInfo; static VALUE cTimestampFactory; static VALUE sBAD_ALG, sBAD_REQUEST, sBAD_DATA_FORMAT, sTIME_NOT_AVAILABLE; static VALUE sUNACCEPTED_POLICY, sUNACCEPTED_EXTENSION, sADD_INFO_NOT_AVAILABLE; static VALUE sSYSTEM_FAILURE; static void ossl_ts_req_free(void *ptr) { TS_REQ_free(ptr); } static const rb_data_type_t ossl_ts_req_type = { "OpenSSL/Timestamp/Request", { 0, ossl_ts_req_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; static void ossl_ts_resp_free(void *ptr) { TS_RESP_free(ptr); } static const rb_data_type_t ossl_ts_resp_type = { "OpenSSL/Timestamp/Response", { 0, ossl_ts_resp_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; static void ossl_ts_token_info_free(void *ptr) { TS_TST_INFO_free(ptr); } static const rb_data_type_t ossl_ts_token_info_type = { "OpenSSL/Timestamp/TokenInfo", { 0, ossl_ts_token_info_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; static VALUE asn1_to_der(void *template, int (*i2d)(void *template, unsigned char **pp)) { VALUE str; int len; unsigned char *p; if((len = i2d(template, NULL)) <= 0) ossl_raise(eTimestampError, "Error when encoding to DER"); str = rb_str_new(0, len); p = (unsigned char *)RSTRING_PTR(str); if(i2d(template, &p) <= 0) ossl_raise(eTimestampError, "Error when encoding to DER"); rb_str_set_len(str, p - (unsigned char*)RSTRING_PTR(str)); return str; } static VALUE obj_to_asn1obj_i(VALUE obj) { return (VALUE)ossl_to_asn1obj(obj); } static VALUE ossl_ts_req_alloc(VALUE klass) { TS_REQ *req; VALUE obj; obj = NewTSRequest(klass); if (!(req = TS_REQ_new())) ossl_raise(eTimestampError, NULL); SetTSRequest(obj, req); /* Defaults */ TS_REQ_set_version(req, 1); TS_REQ_set_cert_req(req, 1); return obj; } /* * When creating a Request with the +File+ or +string+ parameter, the * corresponding +File+ or +string+ must be DER-encoded. * * call-seq: * OpenSSL::Timestamp::Request.new(file) -> request * OpenSSL::Timestamp::Request.new(string) -> request * OpenSSL::Timestamp::Request.new -> empty request */ static VALUE ossl_ts_req_initialize(int argc, VALUE *argv, VALUE self) { TS_REQ *ts_req = 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); ts_req = d2i_TS_REQ_bio(in, &ts_req); BIO_free(in); if (!ts_req) { DATA_PTR(self) = NULL; ossl_raise(eTimestampError, "Error when decoding the timestamp request"); } DATA_PTR(self) = ts_req; return self; } /* * Returns the 'short name' of the object identifier that represents the * algorithm that was used to create the message imprint digest. * * call-seq: * request.algorithm -> string */ static VALUE ossl_ts_req_get_algorithm(VALUE self) { TS_REQ *req; TS_MSG_IMPRINT *mi; X509_ALGOR *algor; const ASN1_OBJECT *obj; GetTSRequest(self, req); mi = TS_REQ_get_msg_imprint(req); algor = TS_MSG_IMPRINT_get_algo(mi); X509_ALGOR_get0(&obj, NULL, NULL, algor); return ossl_asn1obj_to_string(obj); } /* * Allows to set the object identifier or the 'short name' of the * algorithm that was used to create the message imprint digest. * * ===Example: * request.algorithm = "SHA1" * * call-seq: * request.algorithm = "string" -> string */ static VALUE ossl_ts_req_set_algorithm(VALUE self, VALUE algo) { TS_REQ *req; TS_MSG_IMPRINT *mi; ASN1_OBJECT *obj; X509_ALGOR *algor; GetTSRequest(self, req); obj = ossl_to_asn1obj(algo); mi = TS_REQ_get_msg_imprint(req); algor = TS_MSG_IMPRINT_get_algo(mi); if (!X509_ALGOR_set0(algor, obj, V_ASN1_NULL, NULL)) { ASN1_OBJECT_free(obj); ossl_raise(eTimestampError, "X509_ALGOR_set0"); } return algo; } /* * Returns the message imprint (digest) of the data to be timestamped. * * call-seq: * request.message_imprint -> string or nil */ static VALUE ossl_ts_req_get_msg_imprint(VALUE self) { TS_REQ *req; TS_MSG_IMPRINT *mi; ASN1_OCTET_STRING *hashed_msg; VALUE ret; GetTSRequest(self, req); mi = TS_REQ_get_msg_imprint(req); hashed_msg = TS_MSG_IMPRINT_get_msg(mi); ret = asn1str_to_str(hashed_msg); return ret; } /* * Set the message imprint digest. * * call-seq: * request.message_imprint = "string" -> string */ static VALUE ossl_ts_req_set_msg_imprint(VALUE self, VALUE hash) { TS_REQ *req; TS_MSG_IMPRINT *mi; StringValue(hash); GetTSRequest(self, req); mi = TS_REQ_get_msg_imprint(req); if (!TS_MSG_IMPRINT_set_msg(mi, (unsigned char *)RSTRING_PTR(hash), RSTRING_LENINT(hash))) ossl_raise(eTimestampError, "TS_MSG_IMPRINT_set_msg"); return hash; } /* * Returns the version of this request. +1+ is the default value. * * call-seq: * request.version -> Integer */ static VALUE ossl_ts_req_get_version(VALUE self) { TS_REQ *req; GetTSRequest(self, req); return LONG2NUM(TS_REQ_get_version(req)); } /* * Sets the version number for this Request. This should be +1+ for compliant * servers. * * call-seq: * request.version = number -> Integer */ static VALUE ossl_ts_req_set_version(VALUE self, VALUE version) { TS_REQ *req; long ver; if ((ver = NUM2LONG(version)) < 0) ossl_raise(eTimestampError, "version must be >= 0!"); GetTSRequest(self, req); if (!TS_REQ_set_version(req, ver)) ossl_raise(eTimestampError, "TS_REQ_set_version"); return version; } /* * Returns the 'short name' of the object identifier that represents the * timestamp policy under which the server shall create the timestamp. * * call-seq: * request.policy_id -> string or nil */ static VALUE ossl_ts_req_get_policy_id(VALUE self) { TS_REQ *req; GetTSRequest(self, req); if (!TS_REQ_get_policy_id(req)) return Qnil; return ossl_asn1obj_to_string(TS_REQ_get_policy_id(req)); } /* * Allows to set the object identifier that represents the * timestamp policy under which the server shall create the timestamp. This * may be left +nil+, implying that the timestamp server will issue the * timestamp using some default policy. * * ===Example: * request.policy_id = "1.2.3.4.5" * * call-seq: * request.policy_id = "string" -> string */ static VALUE ossl_ts_req_set_policy_id(VALUE self, VALUE oid) { TS_REQ *req; ASN1_OBJECT *obj; int ok; GetTSRequest(self, req); obj = ossl_to_asn1obj(oid); ok = TS_REQ_set_policy_id(req, obj); ASN1_OBJECT_free(obj); if (!ok) ossl_raise(eTimestampError, "TS_REQ_set_policy_id"); return oid; } /* * Returns the nonce (number used once) that the server shall include in its * response. * * call-seq: * request.nonce -> BN or nil */ static VALUE ossl_ts_req_get_nonce(VALUE self) { TS_REQ *req; const ASN1_INTEGER * nonce; GetTSRequest(self, req); if (!(nonce = TS_REQ_get_nonce(req))) return Qnil; return asn1integer_to_num(nonce); } /* * Sets the nonce (number used once) that the server shall include in its * response. If the nonce is set, the server must return the same nonce value in * a valid Response. * * call-seq: * request.nonce = number -> BN */ static VALUE ossl_ts_req_set_nonce(VALUE self, VALUE num) { TS_REQ *req; ASN1_INTEGER *nonce; int ok; GetTSRequest(self, req); nonce = num_to_asn1integer(num, NULL); ok = TS_REQ_set_nonce(req, nonce); ASN1_INTEGER_free(nonce); if (!ok) ossl_raise(eTimestampError, NULL); return num; } /* * Indicates whether the response shall contain the timestamp authority's * certificate or not. * * call-seq: * request.cert_requested? -> true or false */ static VALUE ossl_ts_req_get_cert_requested(VALUE self) { TS_REQ *req; GetTSRequest(self, req); return TS_REQ_get_cert_req(req) ? Qtrue: Qfalse; } /* * Specify whether the response shall contain the timestamp authority's * certificate or not. The default value is +true+. * * call-seq: * request.cert_requested = boolean -> true or false */ static VALUE ossl_ts_req_set_cert_requested(VALUE self, VALUE requested) { TS_REQ *req; GetTSRequest(self, req); TS_REQ_set_cert_req(req, RTEST(requested)); return requested; } /* * DER-encodes this Request. * * call-seq: * request.to_der -> DER-encoded string */ static VALUE ossl_ts_req_to_der(VALUE self) { TS_REQ *req; TS_MSG_IMPRINT *mi; X509_ALGOR *algo; const ASN1_OBJECT *obj; ASN1_OCTET_STRING *hashed_msg; GetTSRequest(self, req); mi = TS_REQ_get_msg_imprint(req); algo = TS_MSG_IMPRINT_get_algo(mi); X509_ALGOR_get0(&obj, NULL, NULL, algo); if (OBJ_obj2nid(obj) == NID_undef) ossl_raise(eTimestampError, "Message imprint missing algorithm"); hashed_msg = TS_MSG_IMPRINT_get_msg(mi); if (!ASN1_STRING_length(hashed_msg)) ossl_raise(eTimestampError, "Message imprint missing hashed message"); return asn1_to_der((void *)req, (int (*)(void *, unsigned char **))i2d_TS_REQ); } static VALUE ossl_ts_req_to_text(VALUE self) { TS_REQ *req; BIO *out; GetTSRequest(self, req); out = BIO_new(BIO_s_mem()); if (!out) ossl_raise(eTimestampError, NULL); if (!TS_REQ_print_bio(out, req)) { BIO_free(out); ossl_raise(eTimestampError, NULL); } return ossl_membio2str(out); } static VALUE ossl_ts_resp_alloc(VALUE klass) { TS_RESP *resp; VALUE obj; obj = NewTSResponse(klass); if (!(resp = TS_RESP_new())) ossl_raise(eTimestampError, NULL); SetTSResponse(obj, resp); return obj; } /* * Creates a Response from a +File+ or +string+ parameter, the * corresponding +File+ or +string+ must be DER-encoded. Please note * that Response is an immutable read-only class. If you'd like to create * timestamps please refer to Factory instead. * * call-seq: * OpenSSL::Timestamp::Response.new(file) -> response * OpenSSL::Timestamp::Response.new(string) -> response */ static VALUE ossl_ts_resp_initialize(VALUE self, VALUE der) { TS_RESP *ts_resp = DATA_PTR(self); BIO *in; der = ossl_to_der_if_possible(der); in = ossl_obj2bio(&der); ts_resp = d2i_TS_RESP_bio(in, &ts_resp); BIO_free(in); if (!ts_resp) { DATA_PTR(self) = NULL; ossl_raise(eTimestampError, "Error when decoding the timestamp response"); } DATA_PTR(self) = ts_resp; return self; } /* * Returns one of GRANTED, GRANTED_WITH_MODS, REJECTION, WAITING, * REVOCATION_WARNING or REVOCATION_NOTIFICATION. A timestamp token has * been created only in case +status+ is equal to GRANTED or GRANTED_WITH_MODS. * * call-seq: * response.status -> BN (never nil) */ static VALUE ossl_ts_resp_get_status(VALUE self) { TS_RESP *resp; TS_STATUS_INFO *si; const ASN1_INTEGER *st; GetTSResponse(self, resp); si = TS_RESP_get_status_info(resp); st = TS_STATUS_INFO_get0_status(si); return asn1integer_to_num(st); } /* * In cases no timestamp token has been created, this field contains further * info about the reason why response creation failed. The method returns either * nil (the request was successful and a timestamp token was created) or one of * the following: * * :BAD_ALG - Indicates that the timestamp server rejects the message * imprint algorithm used in the Request * * :BAD_REQUEST - Indicates that the timestamp server was not able to process * the Request properly * * :BAD_DATA_FORMAT - Indicates that the timestamp server was not able to * parse certain data in the Request * * :TIME_NOT_AVAILABLE - Indicates that the server could not access its time * source * * :UNACCEPTED_POLICY - Indicates that the requested policy identifier is not * recognized or supported by the timestamp server * * :UNACCEPTED_EXTENSIION - Indicates that an extension in the Request is * not supported by the timestamp server * * :ADD_INFO_NOT_AVAILABLE -Indicates that additional information requested * is either not understood or currently not available * * :SYSTEM_FAILURE - Timestamp creation failed due to an internal error that * occurred on the timestamp server * * call-seq: * response.failure_info -> nil or symbol */ static VALUE ossl_ts_resp_get_failure_info(VALUE self) { TS_RESP *resp; TS_STATUS_INFO *si; const ASN1_BIT_STRING *fi; GetTSResponse(self, resp); si = TS_RESP_get_status_info(resp); fi = TS_STATUS_INFO_get0_failure_info(si); if (!fi) return Qnil; if (ASN1_BIT_STRING_get_bit(fi, TS_INFO_BAD_ALG)) return sBAD_ALG; if (ASN1_BIT_STRING_get_bit(fi, TS_INFO_BAD_REQUEST)) return sBAD_REQUEST; if (ASN1_BIT_STRING_get_bit(fi, TS_INFO_BAD_DATA_FORMAT)) return sBAD_DATA_FORMAT; if (ASN1_BIT_STRING_get_bit(fi, TS_INFO_TIME_NOT_AVAILABLE)) return sTIME_NOT_AVAILABLE; if (ASN1_BIT_STRING_get_bit(fi, TS_INFO_UNACCEPTED_POLICY)) return sUNACCEPTED_POLICY; if (ASN1_BIT_STRING_get_bit(fi, TS_INFO_UNACCEPTED_EXTENSION)) return sUNACCEPTED_EXTENSION; if (ASN1_BIT_STRING_get_bit(fi, TS_INFO_ADD_INFO_NOT_AVAILABLE)) return sADD_INFO_NOT_AVAILABLE; if (ASN1_BIT_STRING_get_bit(fi, TS_INFO_SYSTEM_FAILURE)) return sSYSTEM_FAILURE; ossl_raise(eTimestampError, "Unrecognized failure info."); } /* * In cases of failure this field may contain an array of strings further * describing the origin of the failure. * * call-seq: * response.status_text -> Array of strings or nil */ static VALUE ossl_ts_resp_get_status_text(VALUE self) { TS_RESP *resp; TS_STATUS_INFO *si; const STACK_OF(ASN1_UTF8STRING) *text; ASN1_UTF8STRING *current; int i; VALUE ret = rb_ary_new(); GetTSResponse(self, resp); si = TS_RESP_get_status_info(resp); if ((text = TS_STATUS_INFO_get0_text(si))) { for (i = 0; i < sk_ASN1_UTF8STRING_num(text); i++) { current = sk_ASN1_UTF8STRING_value(text, i); rb_ary_push(ret, asn1str_to_str(current)); } } return ret; } /* * If a timestamp token is present, this returns it in the form of a * OpenSSL::PKCS7. * * call-seq: * response.token -> nil or OpenSSL::PKCS7 */ static VALUE ossl_ts_resp_get_token(VALUE self) { TS_RESP *resp; PKCS7 *p7; GetTSResponse(self, resp); if (!(p7 = TS_RESP_get_token(resp))) return Qnil; return ossl_pkcs7_new(p7); } /* * Get the response's token info if present. * * call-seq: * response.token_info -> nil or OpenSSL::Timestamp::TokenInfo */ static VALUE ossl_ts_resp_get_token_info(VALUE self) { TS_RESP *resp; TS_TST_INFO *info, *copy; VALUE obj; GetTSResponse(self, resp); if (!(info = TS_RESP_get_tst_info(resp))) return Qnil; obj = NewTSTokenInfo(cTimestampTokenInfo); if (!(copy = TS_TST_INFO_dup(info))) ossl_raise(eTimestampError, NULL); SetTSTokenInfo(obj, copy); return obj; } /* * If the Request specified to request the TSA certificate * (Request#cert_requested = true), then this field contains the * certificate of the timestamp authority. * * call-seq: * response.tsa_certificate -> OpenSSL::X509::Certificate or nil */ static VALUE ossl_ts_resp_get_tsa_certificate(VALUE self) { TS_RESP *resp; PKCS7 *p7; PKCS7_SIGNER_INFO *ts_info; const X509 *cert; GetTSResponse(self, resp); if (!(p7 = TS_RESP_get_token(resp))) return Qnil; ts_info = sk_PKCS7_SIGNER_INFO_value(p7->d.sign->signer_info, 0); cert = PKCS7_cert_from_signer_info(p7, ts_info); if (!cert) return Qnil; return ossl_x509_new(cert); } /* * Returns the Response in DER-encoded form. * * call-seq: * response.to_der -> string */ static VALUE ossl_ts_resp_to_der(VALUE self) { TS_RESP *resp; GetTSResponse(self, resp); return asn1_to_der((void *)resp, (int (*)(void *, unsigned char **))i2d_TS_RESP); } static VALUE ossl_ts_resp_to_text(VALUE self) { TS_RESP *resp; BIO *out; GetTSResponse(self, resp); out = BIO_new(BIO_s_mem()); if (!out) ossl_raise(eTimestampError, NULL); if (!TS_RESP_print_bio(out, resp)) { BIO_free(out); ossl_raise(eTimestampError, NULL); } return ossl_membio2str(out); } /* * Verifies a timestamp token by checking the signature, validating the * certificate chain implied by tsa_certificate and by checking conformance to * a given Request. Mandatory parameters are the Request associated to this * Response, and an OpenSSL::X509::Store of trusted roots. * * Intermediate certificates can optionally be supplied for creating the * certificate chain. These intermediate certificates must all be * instances of OpenSSL::X509::Certificate. * * If validation fails, several kinds of exceptions can be raised: * * TypeError if types don't fit * * TimestampError if something is wrong with the timestamp token itself, if * it is not conformant to the Request, or if validation of the timestamp * certificate chain fails. * * call-seq: * response.verify(Request, root_store) -> Response * response.verify(Request, root_store, [intermediate_cert]) -> Response */ static VALUE ossl_ts_resp_verify(int argc, VALUE *argv, VALUE self) { VALUE ts_req, store, intermediates; TS_RESP *resp; TS_REQ *req; X509_STORE *x509st; TS_VERIFY_CTX *ctx; STACK_OF(X509) *x509inter = NULL; PKCS7* p7; X509 *cert; int status, i, ok; rb_scan_args(argc, argv, "21", &ts_req, &store, &intermediates); GetTSResponse(self, resp); GetTSRequest(ts_req, req); x509st = GetX509StorePtr(store); if (!(ctx = TS_REQ_to_TS_VERIFY_CTX(req, NULL))) { ossl_raise(eTimestampError, "Error when creating the verification context."); } if (!NIL_P(intermediates)) { x509inter = ossl_protect_x509_ary2sk(intermediates, &status); if (status) { TS_VERIFY_CTX_free(ctx); rb_jump_tag(status); } } else if (!(x509inter = sk_X509_new_null())) { TS_VERIFY_CTX_free(ctx); ossl_raise(eTimestampError, "sk_X509_new_null"); } if (!(p7 = TS_RESP_get_token(resp))) { TS_VERIFY_CTX_free(ctx); sk_X509_pop_free(x509inter, X509_free); ossl_raise(eTimestampError, "TS_RESP_get_token"); } for (i=0; i < sk_X509_num(p7->d.sign->cert); i++) { cert = sk_X509_value(p7->d.sign->cert, i); if (!sk_X509_push(x509inter, cert)) { sk_X509_pop_free(x509inter, X509_free); TS_VERIFY_CTX_free(ctx); ossl_raise(eTimestampError, "sk_X509_push"); } X509_up_ref(cert); } if (!X509_STORE_up_ref(x509st)) { sk_X509_pop_free(x509inter, X509_free); TS_VERIFY_CTX_free(ctx); ossl_raise(eTimestampError, "X509_STORE_up_ref"); } #ifdef HAVE_TS_VERIFY_CTX_SET0_CERTS TS_VERIFY_CTX_set0_certs(ctx, x509inter); TS_VERIFY_CTX_set0_store(ctx, x509st); #else # if OSSL_OPENSSL_PREREQ(3, 0, 0) || OSSL_IS_LIBRESSL TS_VERIFY_CTX_set_certs(ctx, x509inter); # else TS_VERIFY_CTS_set_certs(ctx, x509inter); # endif TS_VERIFY_CTX_set_store(ctx, x509st); #endif TS_VERIFY_CTX_add_flags(ctx, TS_VFY_SIGNATURE); ok = TS_RESP_verify_response(ctx, resp); TS_VERIFY_CTX_free(ctx); if (!ok) ossl_raise(eTimestampError, "TS_RESP_verify_response"); return self; } static VALUE ossl_ts_token_info_alloc(VALUE klass) { TS_TST_INFO *info; VALUE obj; obj = NewTSTokenInfo(klass); if (!(info = TS_TST_INFO_new())) ossl_raise(eTimestampError, NULL); SetTSTokenInfo(obj, info); return obj; } /* * Creates a TokenInfo from a +File+ or +string+ parameter, the * corresponding +File+ or +string+ must be DER-encoded. Please note * that TokenInfo is an immutable read-only class. If you'd like to create * timestamps please refer to Factory instead. * * call-seq: * OpenSSL::Timestamp::TokenInfo.new(file) -> token-info * OpenSSL::Timestamp::TokenInfo.new(string) -> token-info */ static VALUE ossl_ts_token_info_initialize(VALUE self, VALUE der) { TS_TST_INFO *info = DATA_PTR(self); BIO *in; der = ossl_to_der_if_possible(der); in = ossl_obj2bio(&der); info = d2i_TS_TST_INFO_bio(in, &info); BIO_free(in); if (!info) { DATA_PTR(self) = NULL; ossl_raise(eTimestampError, "Error when decoding the timestamp token info"); } DATA_PTR(self) = info; return self; } /* * Returns the version number of the token info. With compliant servers, * this value should be +1+ if present. If status is GRANTED or * GRANTED_WITH_MODS. * * call-seq: * token_info.version -> Integer or nil */ static VALUE ossl_ts_token_info_get_version(VALUE self) { TS_TST_INFO *info; GetTSTokenInfo(self, info); return LONG2NUM(TS_TST_INFO_get_version(info)); } /* * Returns the timestamp policy object identifier of the policy this timestamp * was created under. If status is GRANTED or GRANTED_WITH_MODS, this is never * +nil+. * * ===Example: * id = token_info.policy_id * puts id -> "1.2.3.4.5" * * call-seq: * token_info.policy_id -> string or nil */ static VALUE ossl_ts_token_info_get_policy_id(VALUE self) { TS_TST_INFO *info; GetTSTokenInfo(self, info); return ossl_asn1obj_to_string(TS_TST_INFO_get_policy_id(info)); } /* * Returns the 'short name' of the object identifier representing the algorithm * that was used to derive the message imprint digest. For valid timestamps, * this is the same value that was already given in the Request. If status is * GRANTED or GRANTED_WITH_MODS, this is never +nil+. * * ===Example: * algo = token_info.algorithm * puts algo -> "SHA1" * * call-seq: * token_info.algorithm -> string or nil */ static VALUE ossl_ts_token_info_get_algorithm(VALUE self) { TS_TST_INFO *info; TS_MSG_IMPRINT *mi; X509_ALGOR *algo; const ASN1_OBJECT *obj; GetTSTokenInfo(self, info); mi = TS_TST_INFO_get_msg_imprint(info); algo = TS_MSG_IMPRINT_get_algo(mi); X509_ALGOR_get0(&obj, NULL, NULL, algo); return ossl_asn1obj_to_string(obj); } /* * Returns the message imprint digest. For valid timestamps, * this is the same value that was already given in the Request. * If status is GRANTED or GRANTED_WITH_MODS, this is never +nil+. * * ===Example: * mi = token_info.msg_imprint * puts mi -> "DEADBEEF" * * call-seq: * token_info.msg_imprint -> string. */ static VALUE ossl_ts_token_info_get_msg_imprint(VALUE self) { TS_TST_INFO *info; TS_MSG_IMPRINT *mi; ASN1_OCTET_STRING *hashed_msg; VALUE ret; GetTSTokenInfo(self, info); mi = TS_TST_INFO_get_msg_imprint(info); hashed_msg = TS_MSG_IMPRINT_get_msg(mi); ret = asn1str_to_str(hashed_msg); return ret; } /* * Returns serial number of the timestamp token. This value shall never be the * same for two timestamp tokens issued by a dedicated timestamp authority. * If status is GRANTED or GRANTED_WITH_MODS, this is never +nil+. * * call-seq: * token_info.serial_number -> BN or nil */ static VALUE ossl_ts_token_info_get_serial_number(VALUE self) { TS_TST_INFO *info; GetTSTokenInfo(self, info); return asn1integer_to_num(TS_TST_INFO_get_serial(info)); } /* * Returns time when this timestamp token was created. If status is GRANTED or * GRANTED_WITH_MODS, this is never +nil+. * * call-seq: * token_info.gen_time -> Time */ static VALUE ossl_ts_token_info_get_gen_time(VALUE self) { TS_TST_INFO *info; GetTSTokenInfo(self, info); return asn1time_to_time(TS_TST_INFO_get_time(info)); } /* * If the ordering field is missing, or if the ordering field is present * and set to false, then the genTime field only indicates the time at * which the time-stamp token has been created by the TSA. In such a * case, the ordering of time-stamp tokens issued by the same TSA or * different TSAs is only possible when the difference between the * genTime of the first time-stamp token and the genTime of the second * time-stamp token is greater than the sum of the accuracies of the * genTime for each time-stamp token. * * If the ordering field is present and set to true, every time-stamp * token from the same TSA can always be ordered based on the genTime * field, regardless of the genTime accuracy. * * call-seq: * token_info.ordering -> true, falses or nil */ static VALUE ossl_ts_token_info_get_ordering(VALUE self) { TS_TST_INFO *info; GetTSTokenInfo(self, info); return TS_TST_INFO_get_ordering(info) ? Qtrue : Qfalse; } /* * If the timestamp token is valid then this field contains the same nonce that * was passed to the timestamp server in the initial Request. * * call-seq: * token_info.nonce -> BN or nil */ static VALUE ossl_ts_token_info_get_nonce(VALUE self) { TS_TST_INFO *info; const ASN1_INTEGER *nonce; GetTSTokenInfo(self, info); if (!(nonce = TS_TST_INFO_get_nonce(info))) return Qnil; return asn1integer_to_num(nonce); } /* * Returns the TokenInfo in DER-encoded form. * * call-seq: * token_info.to_der -> string */ static VALUE ossl_ts_token_info_to_der(VALUE self) { TS_TST_INFO *info; GetTSTokenInfo(self, info); return asn1_to_der((void *)info, (int (*)(void *, unsigned char **))i2d_TS_TST_INFO); } static VALUE ossl_ts_token_info_to_text(VALUE self) { TS_TST_INFO *info; BIO *out; GetTSTokenInfo(self, info); out = BIO_new(BIO_s_mem()); if (!out) ossl_raise(eTimestampError, NULL); if (!TS_TST_INFO_print_bio(out, info)) { BIO_free(out); ossl_raise(eTimestampError, NULL); } return ossl_membio2str(out); } static ASN1_INTEGER * ossl_tsfac_serial_cb(struct TS_resp_ctx *ctx, void *data) { ASN1_INTEGER **snptr = (ASN1_INTEGER **)data; ASN1_INTEGER *sn = *snptr; *snptr = NULL; return sn; } static int #if !defined(LIBRESSL_VERSION_NUMBER) ossl_tsfac_time_cb(struct TS_resp_ctx *ctx, void *data, long *sec, long *usec) #else ossl_tsfac_time_cb(struct TS_resp_ctx *ctx, void *data, time_t *sec, long *usec) #endif { *sec = *((long *)data); *usec = 0; return 1; } static VALUE ossl_evp_md_fetch_i(VALUE args_) { VALUE *args = (VALUE *)args_, md_holder; const EVP_MD *md; md = ossl_evp_md_fetch(args[1], &md_holder); rb_ary_push(args[0], md_holder); return (VALUE)md; } static VALUE ossl_obj2bio_i(VALUE arg) { return (VALUE)ossl_obj2bio((VALUE *)arg); } /* * Creates a Response with the help of an OpenSSL::PKey, an * OpenSSL::X509::Certificate and a Request. * * Mandatory parameters for timestamp creation that need to be set in the * Request: * * * Request#algorithm * * Request#message_imprint * * Mandatory parameters that need to be set in the Factory: * * Factory#serial_number * * Factory#gen_time * * Factory#allowed_digests * * In addition one of either Request#policy_id or Factory#default_policy_id * must be set. * * Raises a TimestampError if creation fails, though successfully created error * responses may be returned. * * call-seq: * factory.create_timestamp(key, certificate, request) -> Response */ static VALUE ossl_tsfac_create_ts(VALUE self, VALUE key, VALUE certificate, VALUE request) { VALUE serial_number, def_policy_id, gen_time, additional_certs, allowed_digests, allowed_digests_tmp = Qnil; VALUE str; STACK_OF(X509) *inter_certs; VALUE tsresp, ret = Qnil; EVP_PKEY *sign_key; X509 *tsa_cert; TS_REQ *req; TS_RESP *response = NULL; TS_RESP_CTX *ctx = NULL; BIO *req_bio; ASN1_INTEGER *asn1_serial = NULL; ASN1_OBJECT *def_policy_id_obj = NULL; long lgen_time; const char * err_msg = NULL; int status = 0; tsresp = NewTSResponse(cTimestampResponse); tsa_cert = GetX509CertPtr(certificate); sign_key = GetPrivPKeyPtr(key); GetTSRequest(request, req); gen_time = ossl_tsfac_get_gen_time(self); if (!rb_obj_is_instance_of(gen_time, rb_cTime)) { err_msg = "@gen_time must be a Time."; goto end; } lgen_time = NUM2LONG(rb_funcall(gen_time, rb_intern("to_i"), 0)); serial_number = ossl_tsfac_get_serial_number(self); if (NIL_P(serial_number)) { err_msg = "@serial_number must be set."; goto end; } asn1_serial = num_to_asn1integer(serial_number, NULL); def_policy_id = ossl_tsfac_get_default_policy_id(self); if (NIL_P(def_policy_id) && !TS_REQ_get_policy_id(req)) { err_msg = "No policy id in the request and no default policy set"; goto end; } if (!NIL_P(def_policy_id) && !TS_REQ_get_policy_id(req)) { def_policy_id_obj = (ASN1_OBJECT*)rb_protect(obj_to_asn1obj_i, (VALUE)def_policy_id, &status); if (status) goto end; } if (!(ctx = TS_RESP_CTX_new())) { err_msg = "Memory allocation failed."; goto end; } TS_RESP_CTX_set_serial_cb(ctx, ossl_tsfac_serial_cb, &asn1_serial); if (!TS_RESP_CTX_set_signer_cert(ctx, tsa_cert)) { err_msg = "Certificate does not contain the timestamping extension"; goto end; } additional_certs = ossl_tsfac_get_additional_certs(self); if (rb_obj_is_kind_of(additional_certs, rb_cArray)) { inter_certs = ossl_protect_x509_ary2sk(additional_certs, &status); if (status) goto end; /* this dups the sk_X509 and ups each cert's ref count */ TS_RESP_CTX_set_certs(ctx, inter_certs); sk_X509_pop_free(inter_certs, X509_free); } TS_RESP_CTX_set_signer_key(ctx, sign_key); if (!NIL_P(def_policy_id) && !TS_REQ_get_policy_id(req)) TS_RESP_CTX_set_def_policy(ctx, def_policy_id_obj); if (TS_REQ_get_policy_id(req)) TS_RESP_CTX_set_def_policy(ctx, TS_REQ_get_policy_id(req)); TS_RESP_CTX_set_time_cb(ctx, ossl_tsfac_time_cb, &lgen_time); allowed_digests = ossl_tsfac_get_allowed_digests(self); if (rb_obj_is_kind_of(allowed_digests, rb_cArray)) { allowed_digests_tmp = rb_ary_new_capa(RARRAY_LEN(allowed_digests)); for (long i = 0; i < RARRAY_LEN(allowed_digests); i++) { VALUE args[] = { allowed_digests_tmp, rb_ary_entry(allowed_digests, i), }; const EVP_MD *md = (const EVP_MD *)rb_protect(ossl_evp_md_fetch_i, (VALUE)args, &status); if (status) goto end; if (!TS_RESP_CTX_add_md(ctx, md)) goto end; } } str = rb_protect(ossl_to_der, request, &status); if (status) goto end; req_bio = (BIO*)rb_protect(ossl_obj2bio_i, (VALUE)&str, &status); if (status) goto end; response = TS_RESP_create_response(ctx, req_bio); BIO_free(req_bio); RB_GC_GUARD(allowed_digests_tmp); if (!response) { err_msg = "Error during response generation"; goto end; } /* bad responses aren't exceptional, but openssl still sets error * information. */ ossl_clear_error(); SetTSResponse(tsresp, response); ret = tsresp; end: ASN1_INTEGER_free(asn1_serial); ASN1_OBJECT_free(def_policy_id_obj); TS_RESP_CTX_free(ctx); if (err_msg) rb_exc_raise(ossl_make_error(eTimestampError, rb_str_new_cstr(err_msg))); if (status) rb_jump_tag(status); return ret; } /* * INIT */ void Init_ossl_ts(void) { /* * Possible return value for +Response#failure_info+. Indicates that the * timestamp server rejects the message imprint algorithm used in the * +Request+ */ sBAD_ALG = ID2SYM(rb_intern_const("BAD_ALG")); /* * Possible return value for +Response#failure_info+. Indicates that the * timestamp server was not able to process the +Request+ properly. */ sBAD_REQUEST = ID2SYM(rb_intern_const("BAD_REQUEST")); /* * Possible return value for +Response#failure_info+. Indicates that the * timestamp server was not able to parse certain data in the +Request+. */ sBAD_DATA_FORMAT = ID2SYM(rb_intern_const("BAD_DATA_FORMAT")); sTIME_NOT_AVAILABLE = ID2SYM(rb_intern_const("TIME_NOT_AVAILABLE")); sUNACCEPTED_POLICY = ID2SYM(rb_intern_const("UNACCEPTED_POLICY")); sUNACCEPTED_EXTENSION = ID2SYM(rb_intern_const("UNACCEPTED_EXTENSION")); sADD_INFO_NOT_AVAILABLE = ID2SYM(rb_intern_const("ADD_INFO_NOT_AVAILABLE")); sSYSTEM_FAILURE = ID2SYM(rb_intern_const("SYSTEM_FAILURE")); /* Document-class: OpenSSL::Timestamp * Provides classes and methods to request, create and validate * {RFC3161-compliant}[http://www.ietf.org/rfc/rfc3161.txt] timestamps. * Request may be used to either create requests from scratch or to parse * existing requests that again can be used to request timestamps from a * timestamp server, e.g. via the net/http. The resulting timestamp * response may be parsed using Response. * * Please note that Response is read-only and immutable. To create a * Response, an instance of Factory as well as a valid Request are needed. * * ===Create a Response: * #Assumes ts.p12 is a PKCS#12-compatible file with a private key * #and a certificate that has an extended key usage of 'timeStamping' * p12 = OpenSSL::PKCS12.new(File.binread('ts.p12'), 'pwd') * md = OpenSSL::Digest.new('SHA1') * hash = md.digest(data) #some binary data to be timestamped * req = OpenSSL::Timestamp::Request.new * req.algorithm = 'SHA1' * req.message_imprint = hash * req.policy_id = "1.2.3.4.5" * req.nonce = 42 * fac = OpenSSL::Timestamp::Factory.new * fac.gen_time = Time.now * fac.serial_number = 1 * timestamp = fac.create_timestamp(p12.key, p12.certificate, req) * * ===Verify a timestamp response: * #Assume we have a timestamp token in a file called ts.der * ts = OpenSSL::Timestamp::Response.new(File.binread('ts.der')) * #Assume we have the Request for this token in a file called req.der * req = OpenSSL::Timestamp::Request.new(File.binread('req.der')) * # Assume the associated root CA certificate is contained in a * # DER-encoded file named root.cer * root = OpenSSL::X509::Certificate.new(File.binread('root.cer')) * # get the necessary intermediate certificates, available in * # DER-encoded form in inter1.cer and inter2.cer * inter1 = OpenSSL::X509::Certificate.new(File.binread('inter1.cer')) * inter2 = OpenSSL::X509::Certificate.new(File.binread('inter2.cer')) * ts.verify(req, root, inter1, inter2) -> ts or raises an exception if validation fails * */ mTimestamp = rb_define_module_under(mOSSL, "Timestamp"); /* Document-class: OpenSSL::Timestamp::TimestampError * Generic exception class of the Timestamp module. */ eTimestampError = rb_define_class_under(mTimestamp, "TimestampError", eOSSLError); /* Document-class: OpenSSL::Timestamp::Response * Immutable and read-only representation of a timestamp response returned * from a timestamp server after receiving an associated Request. Allows * access to specific information about the response but also allows to * verify the Response. */ cTimestampResponse = rb_define_class_under(mTimestamp, "Response", rb_cObject); rb_define_alloc_func(cTimestampResponse, ossl_ts_resp_alloc); rb_define_method(cTimestampResponse, "initialize", ossl_ts_resp_initialize, 1); rb_define_method(cTimestampResponse, "status", ossl_ts_resp_get_status, 0); rb_define_method(cTimestampResponse, "failure_info", ossl_ts_resp_get_failure_info, 0); rb_define_method(cTimestampResponse, "status_text", ossl_ts_resp_get_status_text, 0); rb_define_method(cTimestampResponse, "token", ossl_ts_resp_get_token, 0); rb_define_method(cTimestampResponse, "token_info", ossl_ts_resp_get_token_info, 0); rb_define_method(cTimestampResponse, "tsa_certificate", ossl_ts_resp_get_tsa_certificate, 0); rb_define_method(cTimestampResponse, "to_der", ossl_ts_resp_to_der, 0); rb_define_method(cTimestampResponse, "to_text", ossl_ts_resp_to_text, 0); rb_define_method(cTimestampResponse, "verify", ossl_ts_resp_verify, -1); /* Document-class: OpenSSL::Timestamp::TokenInfo * Immutable and read-only representation of a timestamp token info from a * Response. */ cTimestampTokenInfo = rb_define_class_under(mTimestamp, "TokenInfo", rb_cObject); rb_define_alloc_func(cTimestampTokenInfo, ossl_ts_token_info_alloc); rb_define_method(cTimestampTokenInfo, "initialize", ossl_ts_token_info_initialize, 1); rb_define_method(cTimestampTokenInfo, "version", ossl_ts_token_info_get_version, 0); rb_define_method(cTimestampTokenInfo, "policy_id", ossl_ts_token_info_get_policy_id, 0); rb_define_method(cTimestampTokenInfo, "algorithm", ossl_ts_token_info_get_algorithm, 0); rb_define_method(cTimestampTokenInfo, "message_imprint", ossl_ts_token_info_get_msg_imprint, 0); rb_define_method(cTimestampTokenInfo, "serial_number", ossl_ts_token_info_get_serial_number, 0); rb_define_method(cTimestampTokenInfo, "gen_time", ossl_ts_token_info_get_gen_time, 0); rb_define_method(cTimestampTokenInfo, "ordering", ossl_ts_token_info_get_ordering, 0); rb_define_method(cTimestampTokenInfo, "nonce", ossl_ts_token_info_get_nonce, 0); rb_define_method(cTimestampTokenInfo, "to_der", ossl_ts_token_info_to_der, 0); rb_define_method(cTimestampTokenInfo, "to_text", ossl_ts_token_info_to_text, 0); /* Document-class: OpenSSL::Timestamp::Request * Allows to create timestamp requests or parse existing ones. A Request is * also needed for creating timestamps from scratch with Factory. When * created from scratch, some default values are set: * * version is set to +1+ * * cert_requested is set to +true+ * * algorithm, message_imprint, policy_id, and nonce are set to +false+ */ cTimestampRequest = rb_define_class_under(mTimestamp, "Request", rb_cObject); rb_define_alloc_func(cTimestampRequest, ossl_ts_req_alloc); rb_define_method(cTimestampRequest, "initialize", ossl_ts_req_initialize, -1); rb_define_method(cTimestampRequest, "version=", ossl_ts_req_set_version, 1); rb_define_method(cTimestampRequest, "version", ossl_ts_req_get_version, 0); rb_define_method(cTimestampRequest, "algorithm=", ossl_ts_req_set_algorithm, 1); rb_define_method(cTimestampRequest, "algorithm", ossl_ts_req_get_algorithm, 0); rb_define_method(cTimestampRequest, "message_imprint=", ossl_ts_req_set_msg_imprint, 1); rb_define_method(cTimestampRequest, "message_imprint", ossl_ts_req_get_msg_imprint, 0); rb_define_method(cTimestampRequest, "policy_id=", ossl_ts_req_set_policy_id, 1); rb_define_method(cTimestampRequest, "policy_id", ossl_ts_req_get_policy_id, 0); rb_define_method(cTimestampRequest, "nonce=", ossl_ts_req_set_nonce, 1); rb_define_method(cTimestampRequest, "nonce", ossl_ts_req_get_nonce, 0); rb_define_method(cTimestampRequest, "cert_requested=", ossl_ts_req_set_cert_requested, 1); rb_define_method(cTimestampRequest, "cert_requested?", ossl_ts_req_get_cert_requested, 0); rb_define_method(cTimestampRequest, "to_der", ossl_ts_req_to_der, 0); rb_define_method(cTimestampRequest, "to_text", ossl_ts_req_to_text, 0); /* * Indicates a successful response. Equal to +0+. */ rb_define_const(cTimestampResponse, "GRANTED", INT2NUM(TS_STATUS_GRANTED)); /* * Indicates a successful response that probably contains modifications * from the initial request. Equal to +1+. */ rb_define_const(cTimestampResponse, "GRANTED_WITH_MODS", INT2NUM(TS_STATUS_GRANTED_WITH_MODS)); /* * Indicates a failure. No timestamp token was created. Equal to +2+. */ rb_define_const(cTimestampResponse, "REJECTION", INT2NUM(TS_STATUS_REJECTION)); /* * Indicates a failure. No timestamp token was created. Equal to +3+. */ rb_define_const(cTimestampResponse, "WAITING", INT2NUM(TS_STATUS_WAITING)); /* * Indicates a failure. No timestamp token was created. Revocation of a * certificate is imminent. Equal to +4+. */ rb_define_const(cTimestampResponse, "REVOCATION_WARNING", INT2NUM(TS_STATUS_REVOCATION_WARNING)); /* * Indicates a failure. No timestamp token was created. A certificate * has been revoked. Equal to +5+. */ rb_define_const(cTimestampResponse, "REVOCATION_NOTIFICATION", INT2NUM(TS_STATUS_REVOCATION_NOTIFICATION)); /* Document-class: OpenSSL::Timestamp::Factory * * Used to generate a Response from scratch. * * Please bear in mind that the implementation will always apply and prefer * the policy object identifier given in the request over the default policy * id specified in the Factory. As a consequence, +default_policy_id+ will * only be applied if no Request#policy_id was given. But this also means * that one needs to check the policy identifier in the request manually * before creating the Response, e.g. to check whether it complies to a * specific set of acceptable policies. * * There exists also the possibility to add certificates (instances of * OpenSSL::X509::Certificate) besides the timestamping certificate * that will be included in the resulting timestamp token if * Request#cert_requested? is +true+. Ideally, one would also include any * intermediate certificates (the root certificate can be left out - in * order to trust it any verifying party will have to be in its possession * anyway). This simplifies validation of the timestamp since these * intermediate certificates are "already there" and need not be passed as * external parameters to Response#verify anymore, thus minimizing external * resources needed for verification. * * ===Example: Inclusion of (untrusted) intermediate certificates * * Assume we received a timestamp request that has set Request#policy_id to * +nil+ and Request#cert_requested? to true. The raw request bytes are * stored in a variable called +req_raw+. We'd still like to integrate * the necessary intermediate certificates (in +inter1.cer+ and * +inter2.cer+) to simplify validation of the resulting Response. +ts.p12+ * is a PKCS#12-compatible file including the private key and the * timestamping certificate. * * req = OpenSSL::Timestamp::Request.new(raw_bytes) * p12 = OpenSSL::PKCS12.new(File.binread('ts.p12'), 'pwd') * inter1 = OpenSSL::X509::Certificate.new(File.binread('inter1.cer')) * inter2 = OpenSSL::X509::Certificate.new(File.binread('inter2.cer')) * fac = OpenSSL::Timestamp::Factory.new * fac.gen_time = Time.now * fac.serial_number = 1 * fac.allowed_digests = ["sha256", "sha384", "sha512"] * #needed because the Request contained no policy identifier * fac.default_policy_id = '1.2.3.4.5' * fac.additional_certificates = [ inter1, inter2 ] * timestamp = fac.create_timestamp(p12.key, p12.certificate, req) */ cTimestampFactory = rb_define_class_under(mTimestamp, "Factory", rb_cObject); /* * The list of digest algorithms that the factory is allowed * create timestamps for. Known vulnerable or weak algorithms should not be * allowed where possible. Must be an Array of String or OpenSSL::Digest * subclass instances. */ rb_attr(cTimestampFactory, rb_intern_const("allowed_digests"), 1, 1, 0); /* * A String representing the default policy object identifier, or +nil+. * * Request#policy_id will always be preferred over this if present in the * Request, only if Request#policy_id is +nil+ default_policy will be used. * If none of both is present, a TimestampError will be raised when trying * to create a Response. */ rb_attr(cTimestampFactory, rb_intern_const("default_policy_id"), 1, 1, 0); /* * The serial number to be used for timestamp creation. Must be present for * timestamp creation. Must be an instance of OpenSSL::BN or Integer. */ rb_attr(cTimestampFactory, rb_intern_const("serial_number"), 1, 1, 0); /* * The Time value to be used in the Response. Must be present for timestamp * creation. */ rb_attr(cTimestampFactory, rb_intern_const("gen_time"), 1, 1, 0); /* * Additional certificates apart from the timestamp certificate (e.g. * intermediate certificates) to be added to the Response. * Must be an Array of OpenSSL::X509::Certificate, or +nil+. */ rb_attr(cTimestampFactory, rb_intern_const("additional_certs"), 1, 1, 0); rb_define_method(cTimestampFactory, "create_timestamp", ossl_tsfac_create_ts, 3); } #else /* OPENSSL_NO_TS */ void Init_ossl_ts(void) { } #endif ================================================ FILE: ext/openssl/ossl_ts.h ================================================ /* * * Copyright (C) 2010 Martin Bosslet * All rights reserved. */ /* * This program is licenced under the same licence as Ruby. * (See the file 'COPYING'.) */ #if !defined(_OSSL_TS_H_) #define _OSSL_TS_H_ void Init_ossl_ts(void); #endif ================================================ FILE: ext/openssl/ossl_x509.c ================================================ /* * 'OpenSSL for Ruby' project * Copyright (C) 2001-2002 Michal Rokos * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #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_obj_freeze(rb_str_new_cstr(X509_get_default_##i()))) ASN1_TIME * ossl_x509_time_adjust(ASN1_TIME *s, VALUE time) { time_t sec; int off_days; ossl_time_split(time, &sec, &off_days); return X509_time_adj_ex(s, off_days, 0, &sec); } void Init_ossl_x509(void) { 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(); /* Constants are up-to-date with 1.1.1. */ /* Certificate verification error code */ DefX509Const(V_OK); DefX509Const(V_ERR_UNSPECIFIED); 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_UNABLE_TO_GET_CRL_ISSUER); DefX509Const(V_ERR_UNHANDLED_CRITICAL_EXTENSION); DefX509Const(V_ERR_KEYUSAGE_NO_CRL_SIGN); DefX509Const(V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION); DefX509Const(V_ERR_INVALID_NON_CA); DefX509Const(V_ERR_PROXY_PATH_LENGTH_EXCEEDED); DefX509Const(V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE); DefX509Const(V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED); DefX509Const(V_ERR_INVALID_EXTENSION); DefX509Const(V_ERR_INVALID_POLICY_EXTENSION); DefX509Const(V_ERR_NO_EXPLICIT_POLICY); DefX509Const(V_ERR_DIFFERENT_CRL_SCOPE); DefX509Const(V_ERR_UNSUPPORTED_EXTENSION_FEATURE); DefX509Const(V_ERR_UNNESTED_RESOURCE); DefX509Const(V_ERR_PERMITTED_VIOLATION); DefX509Const(V_ERR_EXCLUDED_VIOLATION); DefX509Const(V_ERR_SUBTREE_MINMAX); DefX509Const(V_ERR_APPLICATION_VERIFICATION); DefX509Const(V_ERR_UNSUPPORTED_CONSTRAINT_TYPE); DefX509Const(V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX); DefX509Const(V_ERR_UNSUPPORTED_NAME_SYNTAX); DefX509Const(V_ERR_CRL_PATH_VALIDATION_ERROR); #if defined(X509_V_ERR_PATH_LOOP) /* OpenSSL 1.1.0, missing in LibreSSL */ DefX509Const(V_ERR_PATH_LOOP); #endif #if defined(X509_V_ERR_SUITE_B_INVALID_VERSION) /* OpenSSL 1.1.0, missing in LibreSSL */ DefX509Const(V_ERR_SUITE_B_INVALID_VERSION); DefX509Const(V_ERR_SUITE_B_INVALID_ALGORITHM); DefX509Const(V_ERR_SUITE_B_INVALID_CURVE); DefX509Const(V_ERR_SUITE_B_INVALID_SIGNATURE_ALGORITHM); DefX509Const(V_ERR_SUITE_B_LOS_NOT_ALLOWED); DefX509Const(V_ERR_SUITE_B_CANNOT_SIGN_P_384_WITH_P_256); #endif DefX509Const(V_ERR_HOSTNAME_MISMATCH); DefX509Const(V_ERR_EMAIL_MISMATCH); DefX509Const(V_ERR_IP_ADDRESS_MISMATCH); #if defined(X509_V_ERR_DANE_NO_MATCH) /* OpenSSL 1.1.0, missing in LibreSSL */ DefX509Const(V_ERR_DANE_NO_MATCH); #endif DefX509Const(V_ERR_EE_KEY_TOO_SMALL); DefX509Const(V_ERR_CA_KEY_TOO_SMALL); DefX509Const(V_ERR_CA_MD_TOO_WEAK); DefX509Const(V_ERR_INVALID_CALL); DefX509Const(V_ERR_STORE_LOOKUP); #if defined(X509_V_ERR_NO_VALID_SCTS) /* OpenSSL 1.1.0, missing in LibreSSL */ DefX509Const(V_ERR_NO_VALID_SCTS); #endif #if defined(X509_V_ERR_PROXY_SUBJECT_NAME_VIOLATION) /* OpenSSL 1.1.0, missing in LibreSSL */ DefX509Const(V_ERR_PROXY_SUBJECT_NAME_VIOLATION); #endif #if defined(X509_V_ERR_OCSP_VERIFY_NEEDED) /* OpenSSL 1.1.1, missing in LibreSSL */ DefX509Const(V_ERR_OCSP_VERIFY_NEEDED); DefX509Const(V_ERR_OCSP_VERIFY_FAILED); DefX509Const(V_ERR_OCSP_CERT_UNKNOWN); #endif /* Certificate verify flags */ /* Set by Store#flags= and StoreContext#flags=. */ DefX509Const(V_FLAG_USE_CHECK_TIME); /* 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); /* Set by Store#flags= and StoreContext#flags=. Enables some additional * features including support for indirect signed CRLs. */ DefX509Const(V_FLAG_EXTENDED_CRL_SUPPORT); /* Set by Store#flags= and StoreContext#flags=. Uses delta CRLs. If not * specified, deltas are ignored. */ DefX509Const(V_FLAG_USE_DELTAS); /* Set by Store#flags= and StoreContext#flags=. Enables checking of the * signature of the root self-signed CA. */ DefX509Const(V_FLAG_CHECK_SS_SIGNATURE); /* 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); #if defined(X509_V_FLAG_SUITEB_128_LOS_ONLY) /* OpenSSL 1.1.0, missing in LibreSSL */ /* Set by Store#flags= and StoreContext#flags=. * Enables Suite B 128 bit only mode. */ DefX509Const(V_FLAG_SUITEB_128_LOS_ONLY); /* Set by Store#flags= and StoreContext#flags=. * Enables Suite B 192 bit only mode. */ DefX509Const(V_FLAG_SUITEB_192_LOS); /* Set by Store#flags= and StoreContext#flags=. * Enables Suite B 128 bit mode allowing 192 bit algorithms. */ DefX509Const(V_FLAG_SUITEB_128_LOS); #endif /* Set by Store#flags= and StoreContext#flags=. * Allows partial chains if at least one certificate is in trusted store. */ DefX509Const(V_FLAG_PARTIAL_CHAIN); /* 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); /* 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); /* 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); /* Set by Store#purpose=. Time stamps signer. */ DefX509Const(PURPOSE_TIMESTAMP_SIGN); 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); DefX509Const(TRUST_TSA); 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); } ================================================ FILE: ext/openssl/ossl_x509.h ================================================ /* * 'OpenSSL for Ruby' project * Copyright (C) 2001-2002 Michal Rokos * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #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; VALUE ossl_x509attr_new(const X509_ATTRIBUTE *); X509_ATTRIBUTE *GetX509AttrPtr(VALUE); void Init_ossl_x509attr(void); /* * X509Cert */ extern VALUE cX509Cert; VALUE ossl_x509_new(const X509 *); X509 *GetX509CertPtr(VALUE); X509 *DupX509CertPtr(VALUE); void Init_ossl_x509cert(void); /* * X509CRL */ VALUE ossl_x509crl_new(const X509_CRL *); X509_CRL *GetX509CRLPtr(VALUE); void Init_ossl_x509crl(void); /* * X509Extension */ extern VALUE cX509Ext; VALUE ossl_x509ext_new(const X509_EXTENSION *); X509_EXTENSION *GetX509ExtPtr(VALUE); void Init_ossl_x509ext(void); /* * X509Name */ VALUE ossl_x509name_new(const X509_NAME *); X509_NAME *GetX509NamePtr(VALUE); void Init_ossl_x509name(void); /* * X509Request */ X509_REQ *GetX509ReqPtr(VALUE); void Init_ossl_x509req(void); /* * X509Revoked */ extern VALUE cX509Rev; VALUE ossl_x509revoked_new(const X509_REVOKED *); X509_REVOKED *DupX509RevokedPtr(VALUE); void Init_ossl_x509revoked(void); /* * X509Store and X509StoreContext */ X509_STORE *GetX509StorePtr(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_ */ ================================================ FILE: ext/openssl/ossl_x509attr.c ================================================ /* * 'OpenSSL for Ruby' project * Copyright (C) 2001 Michal Rokos * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #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) /* * Classes */ VALUE cX509Attr; static 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 | RUBY_TYPED_WB_PROTECTED, }; /* * Public */ VALUE ossl_x509attr_new(const X509_ATTRIBUTE *attr) { X509_ATTRIBUTE *new; VALUE obj; obj = NewX509Attr(cX509Attr); /* OpenSSL 1.1.1 takes a non-const pointer */ new = X509_ATTRIBUTE_dup((X509_ATTRIBUTE *)attr); if (!new) ossl_raise(eX509AttrError, "X509_ATTRIBUTE_dup"); SetX509Attr(obj, new); return obj; } X509_ATTRIBUTE * GetX509AttrPtr(VALUE obj) { X509_ATTRIBUTE *attr; GetX509Attr(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; } /* :nodoc: */ static VALUE ossl_x509attr_initialize_copy(VALUE self, VALUE other) { X509_ATTRIBUTE *attr, *attr_other, *attr_new; rb_check_frozen(self); GetX509Attr(self, attr); GetX509Attr(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 * * Returns the OID of the attribute. Returns the short name or the dotted * decimal notation. */ static VALUE ossl_x509attr_get_oid(VALUE self) { X509_ATTRIBUTE *attr; GetX509Attr(self, attr); return ossl_asn1obj_to_string(X509_ATTRIBUTE_get0_object(attr)); } /* * call-seq: * attr.value = asn1 => asn1 */ static VALUE ossl_x509attr_set_value(VALUE self, VALUE value) { X509_ATTRIBUTE *attr; GetX509Attr(self, attr); OSSL_Check_Kind(value, cASN1Data); VALUE der = ossl_to_der(value); const unsigned char *p = (const unsigned char *)RSTRING_PTR(der); STACK_OF(ASN1_TYPE) *sk = d2i_ASN1_SET_ANY(NULL, &p, RSTRING_LEN(der)); if (!sk) ossl_raise(eX509AttrError, "attribute value must be ASN1::Set"); if (X509_ATTRIBUTE_count(attr)) { /* populated, reset first */ const 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) { sk_ASN1_TYPE_pop_free(sk, ASN1_TYPE_free); ossl_raise(eX509AttrError, "X509_ATTRIBUTE_create_by_OBJ"); } SetX509Attr(self, new_attr); X509_ATTRIBUTE_free(attr); attr = new_attr; } for (int i = 0; i < sk_ASN1_TYPE_num(sk); i++) { ASN1_TYPE *a1type = sk_ASN1_TYPE_value(sk, i); if (!X509_ATTRIBUTE_set1_data(attr, ASN1_TYPE_get(a1type), a1type->value.ptr, -1)) { sk_ASN1_TYPE_pop_free(sk, ASN1_TYPE_free); ossl_raise(eX509AttrError, "X509_ATTRIBUTE_set1_data"); } } sk_ASN1_TYPE_pop_free(sk, ASN1_TYPE_free); 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, (ASN1_TYPE *)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) { 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_method(cX509Attr, "initialize_copy", ossl_x509attr_initialize_copy, 1); 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); } ================================================ FILE: ext/openssl/ossl_x509cert.c ================================================ /* * 'OpenSSL for Ruby' project * Copyright (C) 2001-2002 Michal Rokos * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #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) /* * Classes */ VALUE cX509Cert; static 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 | RUBY_TYPED_WB_PROTECTED, }; /* * Public */ VALUE ossl_x509_new(const X509 *x509) { X509 *new; VALUE obj; obj = NewX509(cX509Cert); /* OpenSSL 1.1.1 takes a non-const pointer */ new = X509_dup((X509 *)x509); if (!new) ossl_raise(eX509CertError, "X509_dup"); SetX509(obj, new); return obj; } X509 * GetX509CertPtr(VALUE obj) { X509 *x509; GetX509(obj, x509); return x509; } X509 * DupX509CertPtr(VALUE obj) { X509 *x509; GetX509(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, *x509_orig = RTYPEDDATA_DATA(self); VALUE arg; rb_check_frozen(self); 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 = d2i_X509_bio(in, NULL); if (!x509) { OSSL_BIO_reset(in); x509 = PEM_read_bio_X509(in, NULL, NULL, NULL); } BIO_free(in); if (!x509) ossl_raise(eX509CertError, "PEM_read_bio_X509"); RTYPEDDATA_DATA(self) = x509; X509_free(x509_orig); return self; } /* :nodoc: */ 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); GetX509(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); if (!X509_set_serialNumber(x509, num_to_asn1integer(num, X509_get_serialNumber(x509)))) { ossl_raise(eX509CertError, NULL); } return num; } /* * call-seq: * cert.signature_algorithm => string * * Returns the signature algorithm used to sign this certificate. This returns * the algorithm name found in the TBSCertificate structure, not the outer * \Certificate structure. * * Returns the long name of the signature algorithm, or the dotted decimal * notation if \OpenSSL does not define a long name for it. */ static VALUE ossl_x509_get_signature_algorithm(VALUE self) { X509 *x509; const ASN1_OBJECT *obj; GetX509(self, x509); X509_ALGOR_get0(&obj, NULL, NULL, X509_get0_tbs_sigalg(x509)); return ossl_asn1obj_to_string_long_name(obj); } /* * call-seq: * cert.subject => name */ static VALUE ossl_x509_get_subject(VALUE self) { X509 *x509; const 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; const 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_set1_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_set1_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_wrap(pkey); } /* * call-seq: * cert.public_key = key */ static VALUE ossl_x509_set_public_key(VALUE self, VALUE key) { X509 *x509; EVP_PKEY *pkey; GetX509(self, x509); pkey = GetPKeyPtr(key); ossl_pkey_check_public_key(pkey); if (!X509_set_pubkey(x509, pkey)) ossl_raise(eX509CertError, "X509_set_pubkey"); 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; VALUE md_holder; pkey = GetPrivPKeyPtr(key); /* NO NEED TO DUP */ /* NULL needed for some key types, e.g. Ed25519 */ md = NIL_P(digest) ? NULL : ossl_evp_md_fetch(digest, &md_holder); GetX509(self, x509); if (!X509_sign(x509, pkey, md)) ossl_raise(eX509CertError, "X509_sign"); return self; } /* * call-seq: * cert.verify(key) => true | false * * Verifies the signature of the certificate, with the public key _key_. _key_ * must be an instance of OpenSSL::PKey. */ static VALUE ossl_x509_verify(VALUE self, VALUE key) { X509 *x509; EVP_PKEY *pkey; GetX509(self, x509); pkey = GetPKeyPtr(key); ossl_pkey_check_public_key(pkey); 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) -> true | false * * Returns +true+ if _key_ is the corresponding private key to the Subject * Public Key Information, +false+ otherwise. */ 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; VALUE ary; GetX509(self, x509); count = X509_get_ext_count(x509); ary = rb_ary_new_capa(count); for (i=0; i [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 0; i--) X509_EXTENSION_free(X509_delete_ext(x509, 0)); for (i=0; i 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; } /* * call-seq: * cert1 == cert2 -> true | false * * Compares the two certificates. Note that this takes into account all fields, * not just the issuer name and the serial number. * * This method uses X509_cmp() from OpenSSL, which compares certificates based * on their cached DER encodings. The comparison can be unreliable if a * certificate is incomplete. * * See also the man page X509_cmp(3). */ static VALUE ossl_x509_eq(VALUE self, VALUE other) { X509 *a, *b; GetX509(self, a); if (!rb_obj_is_kind_of(other, cX509Cert)) return Qfalse; GetX509(other, b); return !X509_cmp(a, b) ? Qtrue : Qfalse; } /* * call-seq: * cert.tbs_bytes => string * * Returns the DER-encoded bytes of the certificate's to be signed certificate. * This is mainly useful for validating embedded certificate transparency signatures. */ static VALUE ossl_x509_tbs_bytes(VALUE self) { X509 *x509; int len; unsigned char *p0; VALUE str; GetX509(self, x509); len = i2d_re_X509_tbs(x509, NULL); if (len <= 0) { ossl_raise(eX509CertError, "i2d_re_X509_tbs"); } str = rb_str_new(NULL, len); p0 = (unsigned char *)RSTRING_PTR(str); if (i2d_re_X509_tbs(x509, &p0) <= 0) { ossl_raise(eX509CertError, "i2d_re_X509_tbs"); } ossl_str_adjust(str, p0); return str; } struct load_chained_certificates_arguments { VALUE certificates; X509 *certificate; }; static VALUE load_chained_certificates_append_push(VALUE _arguments) { struct load_chained_certificates_arguments *arguments = (struct load_chained_certificates_arguments*)_arguments; if (arguments->certificates == Qnil) { arguments->certificates = rb_ary_new(); } rb_ary_push(arguments->certificates, ossl_x509_new(arguments->certificate)); return Qnil; } static VALUE load_chained_certificate_append_ensure(VALUE _arguments) { struct load_chained_certificates_arguments *arguments = (struct load_chained_certificates_arguments*)_arguments; X509_free(arguments->certificate); return Qnil; } inline static VALUE load_chained_certificates_append(VALUE certificates, X509 *certificate) { struct load_chained_certificates_arguments arguments; arguments.certificates = certificates; arguments.certificate = certificate; rb_ensure(load_chained_certificates_append_push, (VALUE)&arguments, load_chained_certificate_append_ensure, (VALUE)&arguments); return arguments.certificates; } static VALUE load_chained_certificates_PEM(BIO *in) { VALUE certificates = Qnil; X509 *certificate = PEM_read_bio_X509(in, NULL, NULL, NULL); /* If we cannot read even one certificate: */ if (certificate == NULL) { /* If we cannot read one certificate because we could not read the PEM encoding: */ if (ERR_GET_REASON(ERR_peek_last_error()) == PEM_R_NO_START_LINE) { ossl_clear_error(); } if (ERR_peek_last_error()) ossl_raise(eX509CertError, NULL); else return Qnil; } certificates = load_chained_certificates_append(Qnil, certificate); while ((certificate = PEM_read_bio_X509(in, NULL, NULL, NULL))) { load_chained_certificates_append(certificates, certificate); } /* We tried to read one more certificate but could not read start line: */ if (ERR_GET_REASON(ERR_peek_last_error()) == PEM_R_NO_START_LINE) { /* This is not an error, it means we are finished: */ ossl_clear_error(); return certificates; } /* Alternatively, if we reached the end of the file and there was no error: */ if (BIO_eof(in) && !ERR_peek_last_error()) { return certificates; } else { /* Otherwise, we tried to read a certificate but failed somewhere: */ ossl_raise(eX509CertError, NULL); } } static VALUE load_chained_certificates_DER(BIO *in) { X509 *certificate = d2i_X509_bio(in, NULL); /* If we cannot read one certificate: */ if (certificate == NULL) { /* Ignore error. We could not load. */ ossl_clear_error(); return Qnil; } return load_chained_certificates_append(Qnil, certificate); } static VALUE load_chained_certificates(VALUE _io) { BIO *in = (BIO*)_io; VALUE certificates = Qnil; /* DER is a binary format and it may contain octets within it that look like PEM encoded certificates. So we need to check DER first. */ certificates = load_chained_certificates_DER(in); if (certificates != Qnil) return certificates; OSSL_BIO_reset(in); certificates = load_chained_certificates_PEM(in); if (certificates != Qnil) return certificates; /* Otherwise we couldn't read the output correctly so fail: */ ossl_raise(eX509CertError, "Could not detect format of certificate data!"); } static VALUE load_chained_certificates_ensure(VALUE _io) { BIO *in = (BIO*)_io; BIO_free(in); return Qnil; } /* * call-seq: * OpenSSL::X509::Certificate.load(string) -> [certs...] * OpenSSL::X509::Certificate.load(file) -> [certs...] * * Read the chained certificates from the given input. Supports both PEM * and DER encoded certificates. * * PEM is a text format and supports more than one certificate. * * DER is a binary format and only supports one certificate. * * If the file is empty, or contains only unrelated data, an * +OpenSSL::X509::CertificateError+ exception will be raised. */ static VALUE ossl_x509_load(VALUE klass, VALUE buffer) { BIO *in = ossl_obj2bio(&buffer); return rb_ensure(load_chained_certificates, (VALUE)in, load_chained_certificates_ensure, (VALUE)in); } /* * INIT */ void Init_ossl_x509cert(void) { 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.binread "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.new('SHA256')) * * 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.new('SHA256')) * */ cX509Cert = rb_define_class_under(mX509, "Certificate", rb_cObject); rb_define_singleton_method(cX509Cert, "load", ossl_x509_load, 1); rb_define_alloc_func(cX509Cert, ossl_x509_alloc); rb_define_method(cX509Cert, "initialize", ossl_x509_initialize, -1); rb_define_method(cX509Cert, "initialize_copy", ossl_x509_copy, 1); 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, "==", ossl_x509_eq, 1); rb_define_method(cX509Cert, "tbs_bytes", ossl_x509_tbs_bytes, 0); } ================================================ FILE: ext/openssl/ossl_x509crl.c ================================================ /* * 'OpenSSL for Ruby' project * Copyright (C) 2001-2002 Michal Rokos * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #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) /* * Classes */ static VALUE cX509CRL; static 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 | RUBY_TYPED_WB_PROTECTED, }; /* * PUBLIC */ X509_CRL * GetX509CRLPtr(VALUE obj) { X509_CRL *crl; GetX509CRL(obj, crl); return crl; } VALUE ossl_x509crl_new(const X509_CRL *crl) { X509_CRL *tmp; VALUE obj; obj = NewX509CRL(cX509CRL); /* OpenSSL 1.1.1 takes a non-const pointer */ tmp = X509_CRL_dup((X509_CRL *)crl); if (!tmp) ossl_raise(eX509CRLError, "X509_CRL_dup"); 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, *crl_orig = RTYPEDDATA_DATA(self); VALUE arg; rb_check_frozen(self); if (rb_scan_args(argc, argv, "01", &arg) == 0) { return self; } arg = ossl_to_der_if_possible(arg); in = ossl_obj2bio(&arg); crl = d2i_X509_CRL_bio(in, NULL); if (!crl) { OSSL_BIO_reset(in); crl = PEM_read_bio_X509_CRL(in, NULL, NULL, NULL); } BIO_free(in); if (!crl) ossl_raise(eX509CRLError, "PEM_read_bio_X509_CRL"); RTYPEDDATA_DATA(self) = crl; X509_CRL_free(crl_orig); return self; } /* :nodoc: */ 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); GetX509CRL(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; } /* * call-seq: * crl.signature_algorithm -> string * * Returns the signature algorithm used to sign this CRL. * * Returns the long name of the signature algorithm, or the dotted decimal * notation if \OpenSSL does not define a long name for it. */ static VALUE ossl_x509crl_get_signature_algorithm(VALUE self) { X509_CRL *crl; const X509_ALGOR *alg; const ASN1_OBJECT *obj; GetX509CRL(self, crl); X509_CRL_get0_signature(crl, NULL, &alg); X509_ALGOR_get0(&obj, NULL, NULL, alg); return ossl_asn1obj_to_string_long_name(obj); } 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; const ASN1_TIME *time; GetX509CRL(self, crl); time = X509_CRL_get0_lastUpdate(crl); if (!time) return Qnil; return asn1time_to_time(time); } 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_set1_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; const ASN1_TIME *time; GetX509CRL(self, crl); time = X509_CRL_get0_nextUpdate(crl); if (!time) return Qnil; return asn1time_to_time(time); } 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_set1_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; STACK_OF(X509_REVOKED) *sk; VALUE ary; GetX509CRL(self, crl); sk = X509_CRL_get_REVOKED(crl); if (!sk) return rb_ary_new(); num = sk_X509_REVOKED_num(sk); ary = rb_ary_new_capa(num); for(i=0; i 0; i--) X509_EXTENSION_free(X509_CRL_delete_ext(crl, 0)); for (i=0; i * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #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 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; static VALUE cX509ExtFactory; static 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 | RUBY_TYPED_WB_PROTECTED, }; /* * Public */ VALUE ossl_x509ext_new(const X509_EXTENSION *ext) { X509_EXTENSION *new; VALUE obj; obj = NewX509Ext(cX509Ext); /* OpenSSL 1.1.1 takes a non-const pointer */ new = X509_EXTENSION_dup((X509_EXTENSION *)ext); if (!new) ossl_raise(eX509ExtError, "X509_EXTENSION_dup"); SetX509Ext(obj, new); return obj; } X509_EXTENSION * GetX509ExtPtr(VALUE obj) { X509_EXTENSION *ext; GetX509Ext(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 | RUBY_TYPED_WB_PROTECTED, }; 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; const char *oid_cstr = NULL; rb_scan_args(argc, argv, "21", &oid, &value, &critical); StringValue(value); if(NIL_P(critical)) critical = Qfalse; oid_cstr = StringValueCStr(oid); nid = OBJ_ln2nid(oid_cstr); if (nid != NID_undef) oid_cstr = OBJ_nid2sn(nid); 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 : GetConfig(rconf); X509V3_set_nconf(ctx, conf); ext = X509V3_EXT_nconf(conf, ctx, oid_cstr, RSTRING_PTR(valstr)); X509V3_set_ctx_nodb(ctx); 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(der) * OpenSSL::X509::Extension.new(oid, value) * OpenSSL::X509::Extension.new(oid, value, critical) * * Creates an X509 extension. * * The extension may be created from _der_ data or from an extension _oid_ * and _value_. The _oid_ 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; } /* :nodoc: */ static VALUE ossl_x509ext_initialize_copy(VALUE self, VALUE other) { X509_EXTENSION *ext, *ext_other, *ext_new; rb_check_frozen(self); GetX509Ext(self, ext); GetX509Ext(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 = ASN1_OCTET_STRING_new(); if (!asn1s) ossl_raise(eX509ExtError, "ASN1_OCTET_STRING_new"); if (!ASN1_OCTET_STRING_set(asn1s, (unsigned char *)RSTRING_PTR(data), RSTRING_LENINT(data))) { ASN1_OCTET_STRING_free(asn1s); ossl_raise(eX509ExtError, "ASN1_OCTET_STRING_set"); } if (!X509_EXTENSION_set_data(ext, asn1s)) { ASN1_OCTET_STRING_free(asn1s); ossl_raise(eX509ExtError, "X509_EXTENSION_set_data"); } ASN1_OCTET_STRING_free(asn1s); 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; } /* * call-seq: * ext.oid -> string * * Returns the OID of the extension. Returns the short name or the dotted * decimal notation. */ static VALUE ossl_x509ext_get_oid(VALUE obj) { X509_EXTENSION *ext; GetX509Ext(obj, ext); return ossl_asn1obj_to_string(X509_EXTENSION_get_object(ext)); } 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, X509_EXTENSION_get_data(ext)); ret = ossl_membio2str(out); return ret; } static VALUE ossl_x509ext_get_value_der(VALUE obj) { X509_EXTENSION *ext; const ASN1_OCTET_STRING *value; GetX509Ext(obj, ext); if ((value = X509_EXTENSION_get_data(ext)) == NULL) ossl_raise(eX509ExtError, NULL); return asn1str_to_str(value); } 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) { #undef rb_intern 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_method(cX509Ext, "initialize_copy", ossl_x509ext_initialize_copy, 1); 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, "value_der", ossl_x509ext_get_value_der, 0); rb_define_method(cX509Ext, "critical?", ossl_x509ext_get_critical, 0); rb_define_method(cX509Ext, "to_der", ossl_x509ext_to_der, 0); } ================================================ FILE: ext/openssl/ossl_x509name.c ================================================ /* * 'OpenSSL for Ruby' project * Copyright (C) 2001 Michal Rokos * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #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 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 */ static VALUE cX509Name; static 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 | RUBY_TYPED_WB_PROTECTED, }; /* * Public */ VALUE ossl_x509name_new(const X509_NAME *name) { X509_NAME *new; VALUE obj; obj = NewX509Name(cX509Name); /* OpenSSL 1.1.1 takes a non-const pointer */ new = X509_NAME_dup((X509_NAME *)name); if (!new) ossl_raise(eX509NameError, "X509_NAME_dup"); SetX509Name(obj, new); return obj; } X509_NAME * GetX509NamePtr(VALUE obj) { X509_NAME *name; GetX509Name(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; } /* :nodoc: */ static VALUE ossl_x509name_initialize_copy(VALUE self, VALUE other) { X509_NAME *name, *name_other, *name_new; rb_check_frozen(self); GetX509Name(self, name); GetX509Name(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], loc: -1, set: 0) => 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 * * The optional keyword parameters _loc_ and _set_ specify where to insert the * new attribute. Refer to the manpage of X509_NAME_add_entry(3) for details. * _loc_ defaults to -1 and _set_ defaults to 0. This appends a single-valued * RDN to the end. */ static VALUE ossl_x509name_add_entry(int argc, VALUE *argv, VALUE self) { X509_NAME *name; VALUE oid, value, type, opts, kwargs[2]; static ID kwargs_ids[2]; const char *oid_name; int loc = -1, set = 0; if (!kwargs_ids[0]) { kwargs_ids[0] = rb_intern_const("loc"); kwargs_ids[1] = rb_intern_const("set"); } rb_scan_args(argc, argv, "21:", &oid, &value, &type, &opts); rb_get_kwargs(opts, kwargs_ids, 0, 2, kwargs); oid_name = StringValueCStr(oid); StringValue(value); if(NIL_P(type)) type = rb_aref(OBJECT_TYPE_TEMPLATE, oid); if (kwargs[0] != Qundef) loc = NUM2INT(kwargs[0]); if (kwargs[1] != Qundef) set = NUM2INT(kwargs[1]); GetX509Name(self, name); if (!X509_NAME_add_entry_by_txt(name, oid_name, NUM2INT(type), (unsigned char *)RSTRING_PTR(value), RSTRING_LENINT(value), loc, set)) ossl_raise(eX509NameError, "X509_NAME_add_entry_by_txt"); return self; } static VALUE ossl_x509name_to_s_old(VALUE self) { X509_NAME *name; char *buf; GetX509Name(self, name); buf = X509_NAME_oneline(name, NULL, 0); if (!buf) ossl_raise(eX509NameError, "X509_NAME_oneline"); return ossl_buf2str(buf, rb_long2int(strlen(buf))); } static VALUE x509name_print(VALUE self, unsigned long iflag) { X509_NAME *name; BIO *out; int ret; GetX509Name(self, name); out = BIO_new(BIO_s_mem()); if (!out) ossl_raise(eX509NameError, NULL); ret = X509_NAME_print_ex(out, name, 0, iflag); if (ret < 0 || (iflag == XN_FLAG_COMPAT && ret == 0)) { BIO_free(out); ossl_raise(eX509NameError, "X509_NAME_print_ex"); } return ossl_membio2str(out); } /* * call-seq: * name.to_s -> string * name.to_s(format) -> string * * Returns a String representation of the Distinguished Name. _format_ is * one of: * * * OpenSSL::X509::Name::COMPAT * * OpenSSL::X509::Name::RFC2253 * * OpenSSL::X509::Name::ONELINE * * OpenSSL::X509::Name::MULTILINE * * If _format_ is omitted, the largely broken and traditional OpenSSL format * (X509_NAME_oneline() format) is chosen. * * Use of this method is discouraged. None of the formats other than * OpenSSL::X509::Name::RFC2253 is standardized and may show an inconsistent * behavior through \OpenSSL versions. * * It is recommended to use #to_utf8 instead, which is equivalent to calling * name.to_s(OpenSSL::X509::Name::RFC2253).force_encoding("UTF-8"). */ static VALUE ossl_x509name_to_s(int argc, VALUE *argv, VALUE self) { rb_check_arity(argc, 0, 1); /* name.to_s(nil) was allowed */ if (!argc || NIL_P(argv[0])) return ossl_x509name_to_s_old(self); else return x509name_print(self, NUM2ULONG(argv[0])); } /* * call-seq: * name.to_utf8 -> string * * Returns an UTF-8 representation of the distinguished name, as specified * in {RFC 2253}[https://www.ietf.org/rfc/rfc2253.txt]. */ static VALUE ossl_x509name_to_utf8(VALUE self) { VALUE str = x509name_print(self, XN_FLAG_RFC2253 & ~ASN1_STRFLGS_ESC_MSB); rb_enc_associate_index(str, rb_utf8_encindex()); return str; } /* :nodoc: */ static VALUE ossl_x509name_inspect(VALUE self) { return rb_enc_sprintf(rb_utf8_encoding(), "#<%"PRIsVALUE" %"PRIsVALUE">", rb_obj_class(self), ossl_x509name_to_utf8(self)); } /* * 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; int entries; VALUE ret; GetX509Name(self, name); entries = X509_NAME_entry_count(name); ret = rb_ary_new_capa(entries); for (int i = 0; i < entries; i++) { const X509_NAME_ENTRY *entry = X509_NAME_get_entry(name, i); if (!entry) ossl_raise(eX509NameError, "X509_NAME_get_entry"); const ASN1_OBJECT *obj = X509_NAME_ENTRY_get_object(entry); VALUE vname = ossl_asn1obj_to_string(obj); const ASN1_STRING *data = X509_NAME_ENTRY_get_data(entry); VALUE vdata = asn1str_to_str(data); VALUE type = INT2NUM(ASN1_STRING_type(data)); rb_ary_push(ret, rb_ary_new_from_args(3, vname, vdata, type)); } return ret; } static int ossl_x509name_cmp0(VALUE self, VALUE other) { X509_NAME *name1, *name2; int result; GetX509Name(self, name1); GetX509Name(other, name2); result = X509_NAME_cmp(name1, name2); if (result == -2) { ossl_raise(eX509NameError, NULL); } return result; } /* * call-seq: * name.cmp(other) -> -1 | 0 | 1 | nil * name <=> other -> -1 | 0 | 1 | nil * * 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. * Returns +nil+ if they are not comparable (i.e. different types). */ static VALUE ossl_x509name_cmp(VALUE self, VALUE other) { int result; if (!rb_obj_is_kind_of(other, cX509Name)) return Qnil; result = ossl_x509name_cmp0(self, other); if (result < 0) return INT2FIX(-1); if (result > 0) return INT2FIX(1); return INT2FIX(0); } /* * call-seq: * name.eql?(other) -> true | false * * 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); } /* * 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); } /* * 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_rfc2253 'DC=example,CN=nobody' * * name = OpenSSL::X509::Name.new [['CN', 'nobody'], ['DC', 'example']] */ void Init_ossl_x509name(void) { #undef rb_intern VALUE utf8str, ptrstr, ia5str, hash; 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_method(cX509Name, "initialize_copy", ossl_x509name_initialize_copy, 1); 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_utf8", ossl_x509name_to_utf8, 0); rb_define_method(cX509Name, "inspect", ossl_x509name_inspect, 0); 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); rb_define_method(cX509Name, "hash_old", ossl_x509name_hash_old, 0); 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); rb_obj_freeze(hash); /* * 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)); } ================================================ FILE: ext/openssl/ossl_x509req.c ================================================ /* * 'OpenSSL for Ruby' project * Copyright (C) 2001-2002 Michal Rokos * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #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) /* * Classes */ static VALUE cX509Req; static 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 | RUBY_TYPED_WB_PROTECTED, }; /* * Public functions */ X509_REQ * GetX509ReqPtr(VALUE obj) { X509_REQ *req; GetX509Req(obj, req); return req; } /* * 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, *req_orig = RTYPEDDATA_DATA(self); VALUE arg; rb_check_frozen(self); if (rb_scan_args(argc, argv, "01", &arg) == 0) { return self; } arg = ossl_to_der_if_possible(arg); in = ossl_obj2bio(&arg); req = d2i_X509_REQ_bio(in, NULL); if (!req) { OSSL_BIO_reset(in); req = PEM_read_bio_X509_REQ(in, NULL, NULL, NULL); } BIO_free(in); if (!req) ossl_raise(eX509ReqError, "PEM_read_bio_X509_REQ"); RTYPEDDATA_DATA(self) = req; X509_REQ_free(req_orig); return self; } /* :nodoc: */ 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); GetX509Req(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; const 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; } /* * call-seq: * req.signature_algorithm -> string * * Returns the signature algorithm used to sign this request. * * Returns the long name of the signature algorithm, or the dotted decimal * notation if \OpenSSL does not define a long name for it. */ static VALUE ossl_x509req_get_signature_algorithm(VALUE self) { X509_REQ *req; const X509_ALGOR *alg; const ASN1_OBJECT *obj; GetX509Req(self, req); X509_REQ_get0_signature(req, NULL, &alg); X509_ALGOR_get0(&obj, NULL, NULL, alg); return ossl_asn1obj_to_string_long_name(obj); } 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_wrap(pkey); } static VALUE ossl_x509req_set_public_key(VALUE self, VALUE key) { X509_REQ *req; EVP_PKEY *pkey; GetX509Req(self, req); pkey = GetPKeyPtr(key); ossl_pkey_check_public_key(pkey); if (!X509_REQ_set_pubkey(req, pkey)) ossl_raise(eX509ReqError, "X509_REQ_set_pubkey"); return key; } static VALUE ossl_x509req_sign(VALUE self, VALUE key, VALUE digest) { X509_REQ *req; EVP_PKEY *pkey; const EVP_MD *md; VALUE md_holder; GetX509Req(self, req); pkey = GetPrivPKeyPtr(key); /* NO NEED TO DUP */ /* NULL needed for some key types, e.g. Ed25519 */ md = NIL_P(digest) ? NULL : ossl_evp_md_fetch(digest, &md_holder); if (!X509_REQ_sign(req, pkey, md)) ossl_raise(eX509ReqError, "X509_REQ_sign"); 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); ossl_pkey_check_public_key(pkey); 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; const 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 0; i--) X509_ATTRIBUTE_free(X509_REQ_delete_attr(req, 0)); for (i=0;i * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #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) /* * Classes */ VALUE cX509Rev; static 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 | RUBY_TYPED_WB_PROTECTED, }; /* * PUBLIC */ VALUE ossl_x509revoked_new(const X509_REVOKED *rev) { X509_REVOKED *new; VALUE obj; obj = NewX509Rev(cX509Rev); /* OpenSSL 1.1.1 takes a non-const pointer */ new = X509_REVOKED_dup((X509_REVOKED *)rev); if (!new) ossl_raise(eX509RevError, "X509_REVOKED_dup"); SetX509Rev(obj, new); return obj; } X509_REVOKED * DupX509RevokedPtr(VALUE obj) { X509_REVOKED *rev, *new; GetX509Rev(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; } /* :nodoc: */ static VALUE ossl_x509revoked_initialize_copy(VALUE self, VALUE other) { X509_REVOKED *rev, *rev_other, *rev_new; rb_check_frozen(self); GetX509Rev(self, rev); GetX509Rev(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; const ASN1_TIME *time; GetX509Rev(self, rev); time = X509_REVOKED_get0_revocationDate(rev); if (!time) return Qnil; return asn1time_to_time(time); } 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; const X509_EXTENSION *ext; VALUE ary; GetX509Rev(self, rev); count = X509_REVOKED_get_ext_count(rev); ary = rb_ary_new_capa(count); for (i=0; i 0; i--) X509_EXTENSION_free(X509_REVOKED_delete_ext(rev, 0)); for (i=0; i * All rights reserved. */ /* * This program is licensed under the same licence as Ruby. * (See the file 'COPYING'.) */ #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 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) /* * 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 ossl_x509stctx_new_i(VALUE arg) { return ossl_x509stctx_new((X509_STORE_CTX *)arg); } static VALUE call_verify_cb_proc(VALUE arg) { struct ossl_verify_cb_args *args = (struct ossl_verify_cb_args *)arg; 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(ossl_x509stctx_new_i, (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(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 */ static VALUE cX509Store; static VALUE cX509StoreContext; static VALUE eX509StoreError; static void ossl_x509store_mark(void *ptr) { X509_STORE *store = ptr; // Note: this reference is stored as @verify_callback so we don't need to mark it. // However we do need to ensure GC compaction won't move it, hence why // we call rb_gc_mark here. rb_gc_mark((VALUE)X509_STORE_get_ex_data(store, store_ex_verify_cb_idx)); } static void ossl_x509store_free(void *ptr) { X509_STORE_free(ptr); } static const rb_data_type_t ossl_x509store_type = { "OpenSSL/X509/STORE", { ossl_x509store_mark, ossl_x509store_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; /* * Public functions */ X509_STORE * GetX509StorePtr(VALUE obj) { X509_STORE *store; GetX509Store(obj, 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, "X509_STORE_new"); 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); if (!X509_STORE_set_ex_data(store, store_ex_verify_cb_idx, (void *)cb)) ossl_raise(eX509StoreError, "X509_STORE_set_ex_data"); rb_iv_set(self, "@verify_callback", cb); RB_OBJ_WRITTEN(self, Qundef, 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; GetX509Store(self, store); if (argc != 0) rb_warn("OpenSSL::X509::Store.new does not take any arguments"); 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); return self; } /* * call-seq: * store.flags = flags * * Sets the default flags used by certificate chain verification performed with * the Store. * * _flags_ consists of zero or more of the constants defined in OpenSSL::X509 * with name V_FLAG_* or'ed together. * * OpenSSL::X509::StoreContext#flags= can be used to change the flags for a * single verification operation. * * See also the man page X509_VERIFY_PARAM_set_flags(3). */ 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 default verification purpose. If specified, * the verifications on the store will check every 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 * * OpenSSL::X509::StoreContext#purpose= can be used to change the value for a * single verification operation. * * See also the man page X509_VERIFY_PARAM_set_purpose(3). */ 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 * * Sets the default trust settings used by the certificate verification with * the store. * * OpenSSL::X509::StoreContext#trust= can be used to change the value for a * single verification operation. * * See also the man page X509_VERIFY_PARAM_set_trust(3). */ 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 the certificate verifications with the store. * By default, if not specified, the current system time is used. * * OpenSSL::X509::StoreContext#time= can be used to change the value for a * single verification operation. * * See also the man page X509_VERIFY_PARAM_set_time(3). */ static VALUE ossl_x509store_set_time(VALUE self, VALUE time) { X509_STORE *store; X509_VERIFY_PARAM *param; GetX509Store(self, store); param = X509_STORE_get0_param(store); X509_VERIFY_PARAM_set_time(param, NUM2LONG(rb_Integer(time))); return time; } /* * call-seq: * store.add_file(file) -> self * * Adds the certificates in _file_ to the certificate store. _file_ is the path * to the file, and the file contains one or more certificates in PEM format * concatenated together. * * See also the man page X509_LOOKUP_file(3). */ static VALUE ossl_x509store_add_file(VALUE self, VALUE file) { X509_STORE *store; X509_LOOKUP *lookup; const char *path; GetX509Store(self, store); path = StringValueCStr(file); lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file()); if (!lookup) ossl_raise(eX509StoreError, "X509_STORE_add_lookup"); if (X509_LOOKUP_load_file(lookup, path, X509_FILETYPE_PEM) != 1) ossl_raise(eX509StoreError, "X509_LOOKUP_load_file"); return self; } /* * call-seq: * store.add_path(path) -> self * * Adds _path_ as the hash dir to be looked up by the store. * * See also the man page X509_LOOKUP_hash_dir(3). */ static VALUE ossl_x509store_add_path(VALUE self, VALUE dir) { X509_STORE *store; X509_LOOKUP *lookup; const char *path; GetX509Store(self, store); path = StringValueCStr(dir); lookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir()); if (!lookup) ossl_raise(eX509StoreError, "X509_STORE_add_lookup"); if (X509_LOOKUP_add_dir(lookup, path, X509_FILETYPE_PEM) != 1) ossl_raise(eX509StoreError, "X509_LOOKUP_add_dir"); 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 * * See also the man page X509_STORE_set_default_paths(3). */ 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, "X509_STORE_set_default_paths"); return Qnil; } /* * call-seq: * store.add_cert(cert) -> self * * Adds the OpenSSL::X509::Certificate _cert_ to the certificate store. * * See also the man page X509_STORE_add_cert(3). */ 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, "X509_STORE_add_cert"); return self; } /* * call-seq: * store.add_crl(crl) -> self * * Adds the OpenSSL::X509::CRL _crl_ to the store. * * See also the man page X509_STORE_add_crl(3). */ 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, "X509_STORE_add_crl"); 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 resulting 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; } /* * Private functions */ static void ossl_x509stctx_mark(void *ptr) { X509_STORE_CTX *ctx = ptr; // Note: this reference is stored as @verify_callback so we don't need to mark it. // However we do need to ensure GC compaction won't move it, hence why // we call rb_gc_mark here. rb_gc_mark((VALUE)X509_STORE_CTX_get_ex_data(ctx, stctx_ex_verify_cb_idx)); } static void ossl_x509stctx_free(void *ptr) { X509_STORE_CTX *ctx = ptr; sk_X509_pop_free(X509_STORE_CTX_get0_untrusted(ctx), X509_free); X509_free((X509 *)X509_STORE_CTX_get0_cert(ctx)); X509_STORE_CTX_free(ctx); } static const rb_data_type_t ossl_x509stctx_type = { "OpenSSL/X509/STORE_CTX", { ossl_x509stctx_mark, ossl_x509stctx_free, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, }; 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, "X509_STORE_CTX_new"); 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); /* * call-seq: * StoreContext.new(store, cert = nil, untrusted = nil) * * Sets up a StoreContext for a verification of the X.509 certificate _cert_. */ static VALUE ossl_x509stctx_initialize(int argc, VALUE *argv, VALUE self) { VALUE store, cert, chain; X509_STORE_CTX *ctx; X509_STORE *x509st; X509 *x509 = NULL; STACK_OF(X509) *x509s = NULL; int state; rb_scan_args(argc, argv, "12", &store, &cert, &chain); GetX509StCtx(self, ctx); GetX509Store(store, x509st); if (!NIL_P(cert)) x509 = DupX509CertPtr(cert); /* NEED TO DUP */ if (!NIL_P(chain)) { x509s = ossl_protect_x509_ary2sk(chain, &state); if (state) { X509_free(x509); rb_jump_tag(state); } } if (X509_STORE_CTX_init(ctx, x509st, x509, x509s) != 1){ X509_free(x509); sk_X509_pop_free(x509s, X509_free); ossl_raise(eX509StoreError, "X509_STORE_CTX_init"); } 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 * * Performs the certificate verification using the parameters set to _stctx_. * * See also the man page X509_verify_cert(3). */ static VALUE ossl_x509stctx_verify(VALUE self) { X509_STORE_CTX *ctx; GetX509StCtx(self, ctx); VALUE cb = rb_iv_get(self, "@verify_callback"); if (!X509_STORE_CTX_set_ex_data(ctx, stctx_ex_verify_cb_idx, (void *)cb)) ossl_raise(eX509StoreError, "X509_STORE_CTX_set_ex_data"); RB_OBJ_WRITTEN(self, Qundef, cb); switch (X509_verify_cert(ctx)) { case 1: return Qtrue; case 0: ossl_clear_error(); return Qfalse; default: ossl_raise(eX509StoreError, "X509_verify_cert"); } } /* * call-seq: * stctx.chain -> nil | Array of X509::Certificate * * Returns the verified chain. * * See also the man page X509_STORE_CTX_set0_verified_chain(3). */ static VALUE ossl_x509stctx_get_chain(VALUE self) { X509_STORE_CTX *ctx; const STACK_OF(X509) *chain; GetX509StCtx(self, ctx); chain = X509_STORE_CTX_get0_chain(ctx); if (!chain) return Qnil; /* Could be an empty array instead? */ return ossl_x509_sk2ary(chain); } /* * call-seq: * stctx.error -> Integer * * Returns the error code of _stctx_. This is typically called after #verify * is done, or from the verification callback set to * OpenSSL::X509::Store#verify_callback=. * * See also the man page X509_STORE_CTX_get_error(3). */ 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 * * Sets the error code of _stctx_. This is used by the verification callback * set to OpenSSL::X509::Store#verify_callback=. * * See also the man page X509_STORE_CTX_set_error(3). */ 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 human readable error string corresponding to the error code * retrieved by #error. * * See also the man page X509_verify_cert_error_string(3). */ 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 * * Returns the depth of the chain. This is used in combination with #error. * * See also the man page X509_STORE_CTX_get_error_depth(3). */ 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 * * Returns the certificate which caused the error. * * See also the man page X509_STORE_CTX_get_current_cert(3). */ static VALUE ossl_x509stctx_get_curr_cert(VALUE self) { X509_STORE_CTX *ctx; const X509 *x509; GetX509StCtx(self, ctx); x509 = X509_STORE_CTX_get_current_cert(ctx); if (!x509) return Qnil; return ossl_x509_new(x509); } /* * call-seq: * stctx.current_crl -> X509::CRL * * Returns the CRL which caused the error. * * See also the man page X509_STORE_CTX_get_current_crl(3). */ static VALUE ossl_x509stctx_get_curr_crl(VALUE self) { X509_STORE_CTX *ctx; const 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. This overrides the default value * set by Store#flags=. * * See also the man page X509_VERIFY_PARAM_set_flags(3). */ 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. This overrides the default value set by * Store#purpose=. * * See also the man page X509_VERIFY_PARAM_set_purpose(3). */ 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 * * Sets the trust settings of the context. This overrides the default value set * by Store#trust=. * * See also the man page X509_VERIFY_PARAM_set_trust(3). */ 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. * * See also the man page X509_VERIFY_PARAM_set_time(3). */ 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) { #undef rb_intern /* 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 certificate in the chain and can be used to implement custom * certificate verification conditions. * * 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 can use StoreContext#error= to change the error code as * needed. The callback must return either true or false. * * NOTE: any exception raised within the callback will be ignored. * * See also the man page X509_STORE_CTX_set_verify_cb(3). */ rb_attr(cX509Store, rb_intern("verify_callback"), 1, 0, Qfalse); /* * The error code set by the last call of #verify. * * See also StoreContext#error. */ rb_attr(cX509Store, rb_intern("error"), 1, 0, Qfalse); /* * The description for the error code set by the last call of #verify. * * See also StoreContext#error_string. */ rb_attr(cX509Store, rb_intern("error_string"), 1, 0, Qfalse); /* * The certificate chain constructed by the last call of #verify. * * See also StoreContext#chain. */ 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); } ================================================ FILE: lib/openssl/bn.rb ================================================ # frozen_string_literal: true #-- # # = Ruby-space definitions that completes C-space funcs for BN # # = Info # 'OpenSSL for Ruby 2' project # Copyright (C) 2002 Michal Rokos # All rights reserved. # # = Licence # This program is licensed under the same licence as Ruby. # (See the file 'COPYING'.) #++ 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 ================================================ FILE: lib/openssl/buffering.rb ================================================ # coding: binary # frozen_string_literal: true #-- #= Info # 'OpenSSL for Ruby 2' project # Copyright (C) 2001 GOTOU YUUZOU # All rights reserved. # #= Licence # This program is licensed under the same licence as Ruby. # (See the file 'COPYING'.) #++ ## # 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 # A buffer which will retain binary encoding. class Buffer < String unless String.method_defined?(:append_as_bytes) alias_method :_append, :<< def append_as_bytes(string) if string.encoding == Encoding::BINARY _append(string) else _append(string.b) end self end end undef_method :concat undef_method :<< end ## # 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 = Buffer.new @sync = @io.sync end # # for reading. # private ## # Fills the buffer from the underlying SSLSocket def fill_rbuff begin @rbuffer.append_as_bytes(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 @rbuffer.slice!(0, size) end end public # call-seq: # ssl.getbyte => 81 # # Get the next 8bit byte from `ssl`. Returns `nil` on EOF def getbyte read(1)&.ord end # Get the next 8bit byte. Raises EOFError on EOF def readbyte raise EOFError if eof? getbyte end ## # 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 a keyword argument _exception_ to +false+, you can indicate # that read_nonblock should not raise an IO::Wait*able exception, but # return the symbol +:wait_writable+ or +:wait_readable+ instead. At EOF, # it will return +nil+ instead of raising EOFError. 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, chomp: false) 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 line = consume_rbuff(size) if chomp && line line.chomp!(eol) end line 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 = Buffer.new unless defined? @wbuffer @wbuffer.append_as_bytes(s) @sync ||= false buffer_size = @wbuffer.bytesize if @sync or buffer_size > BLOCK_SIZE nwrote = 0 begin while nwrote < buffer_size do begin chunk = if nwrote > 0 @wbuffer.byteslice(nwrote, @wbuffer.bytesize) else @wbuffer end nwrote += syswrite(chunk) rescue Errno::EAGAIN retry end end ensure if nwrote < @wbuffer.bytesize @wbuffer[0, nwrote] = "" else @wbuffer.clear end end end end public ## # Writes _s_ to the stream. If the argument is not a String it will be # converted using +.to_s+ method. Returns the number of bytes written. def write(*s) s.inject(0) do |written, str| do_write(str) written + str.bytesize end 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 a keyword argument _exception_ to +false+, you can 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 # +.to_s+ method. 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 = Buffer.new if args.empty? s.append_as_bytes("\n") end args.each{|arg| s.append_as_bytes(arg.to_s) s.sub!(/(? # All rights reserved. # # = Licence # This program is licensed under the same licence as Ruby. # (See the file 'COPYING'.) #++ 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 ================================================ FILE: lib/openssl/digest.rb ================================================ # frozen_string_literal: true #-- # = Ruby-space predefined Digest subclasses # # = Info # 'OpenSSL for Ruby 2' project # Copyright (C) 2002 Michal Rokos # All rights reserved. # # = Licence # This program is licensed under the same licence as Ruby. # (See the file 'COPYING'.) #++ module OpenSSL class Digest # Return the hash value computed with _name_ Digest. _name_ is either the # long name or short name of a supported digest algorithm. # # === Example # # OpenSSL::Digest.digest("SHA256", "abc") def self.digest(name, data) super(data, name) end %w(MD4 MD5 RIPEMD160 SHA1 SHA224 SHA256 SHA384 SHA512).each do |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.tr('-', '_'), klass) end # 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 # # OpenSSL::Digest("Foo") # # => NameError: wrong constant name Foo def Digest(name) OpenSSL::Digest.const_get(name) end module_function :Digest end # OpenSSL ================================================ FILE: lib/openssl/hmac.rb ================================================ # frozen_string_literal: true module OpenSSL class HMAC # Securely compare with another HMAC instance in constant time. def ==(other) return false unless HMAC === other return false unless self.digest.bytesize == other.digest.bytesize OpenSSL.fixed_length_secure_compare(self.digest, other.digest) end # :call-seq: # hmac.base64digest -> string # # Returns the authentication code an a Base64-encoded string. def base64digest [digest].pack("m0") end class << self # :call-seq: # HMAC.digest(digest, key, data) -> aString # # Returns the authentication code as a binary string. The _digest_ parameter # specifies the digest algorithm to use. This may be a String representing # the algorithm name or an instance of OpenSSL::Digest. # # === Example # key = 'key' # data = 'The quick brown fox jumps over the lazy dog' # # hmac = OpenSSL::HMAC.digest('SHA1', key, data) # #=> "\xDE|\x9B\x85\xB8\xB7\x8A\xA6\xBC\x8Az6\xF7\n\x90p\x1C\x9D\xB4\xD9" def digest(digest, key, data) hmac = new(key, digest) hmac << data hmac.digest end # :call-seq: # HMAC.hexdigest(digest, key, data) -> aString # # Returns the authentication code as a hex-encoded string. The _digest_ # parameter specifies the digest algorithm to use. This may be a String # representing the algorithm name or an instance of OpenSSL::Digest. # # === Example # key = 'key' # data = 'The quick brown fox jumps over the lazy dog' # # hmac = OpenSSL::HMAC.hexdigest('SHA1', key, data) # #=> "de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9" def hexdigest(digest, key, data) hmac = new(key, digest) hmac << data hmac.hexdigest end # :call-seq: # HMAC.base64digest(digest, key, data) -> aString # # Returns the authentication code as a Base64-encoded string. The _digest_ # parameter specifies the digest algorithm to use. This may be a String # representing the algorithm name or an instance of OpenSSL::Digest. # # === Example # key = 'key' # data = 'The quick brown fox jumps over the lazy dog' # # hmac = OpenSSL::HMAC.base64digest('SHA1', key, data) # #=> "3nybhbi3iqa8ino29wqQcBydtNk=" def base64digest(digest, key, data) [digest(digest, key, data)].pack("m0") end end end end ================================================ FILE: lib/openssl/marshal.rb ================================================ # frozen_string_literal: true #-- # = Ruby-space definitions to add DER (de)serialization to classes # # = Info # 'OpenSSL for Ruby 2' project # Copyright (C) 2002 Michal Rokos # All rights reserved. # # = Licence # This program is licensed under the same licence as Ruby. # (See the file 'COPYING'.) #++ module OpenSSL module Marshal def self.included(base) base.extend(ClassMethods) end module ClassMethods def _load(string) new(string) end end def _dump(_level) to_der end end end ================================================ FILE: lib/openssl/pkcs5.rb ================================================ # frozen_string_literal: true #-- # Ruby/OpenSSL Project # Copyright (C) 2017 Ruby/OpenSSL Project Authors #++ module OpenSSL module PKCS5 module_function # OpenSSL::PKCS5.pbkdf2_hmac has been renamed to OpenSSL::KDF.pbkdf2_hmac. # This method is provided for backwards compatibility. def pbkdf2_hmac(pass, salt, iter, keylen, digest) OpenSSL::KDF.pbkdf2_hmac(pass, salt: salt, iterations: iter, length: keylen, hash: digest) end def pbkdf2_hmac_sha1(pass, salt, iter, keylen) pbkdf2_hmac(pass, salt, iter, keylen, "sha1") end end end ================================================ FILE: lib/openssl/pkey.rb ================================================ # frozen_string_literal: true #-- # Ruby/OpenSSL Project # Copyright (C) 2017 Ruby/OpenSSL Project Authors #++ require_relative 'marshal' module OpenSSL::PKey # Alias of PKeyError. Before version 4.0.0, this was a subclass of PKeyError. DHError = PKeyError class DH include OpenSSL::Marshal # :call-seq: # dh.public_key -> dhnew # # Returns a new DH instance that carries just the \DH parameters. # # Contrary to the method name, the returned DH object contains only # parameters and not the public key. # # This method is provided for backwards compatibility. In most cases, there # is no need to call this method. # # For the purpose of re-generating the key pair while keeping the # parameters, check OpenSSL::PKey.generate_key. # # Example: # # OpenSSL::PKey::DH.generate by default generates a random key pair # dh1 = OpenSSL::PKey::DH.generate(2048) # p dh1.priv_key #=> # # dhcopy = dh1.public_key # p dhcopy.priv_key #=> nil def public_key DH.new(to_der) end # :call-seq: # dh.params -> hash # # Stores all parameters of key to a Hash. # # The hash has keys 'p', 'q', 'g', 'pub_key', and 'priv_key'. def params %w{p q g pub_key priv_key}.map { |name| [name, send(name)] }.to_h end # :call-seq: # dh.compute_key(pub_bn) -> string # # Returns a String containing a shared secret computed from the other # party's public value. # # This method is provided for backwards compatibility, and calls #derive # internally. # # === Parameters # * _pub_bn_ is a OpenSSL::BN, *not* the DH instance returned by # DH#public_key as that contains the DH parameters only. def compute_key(pub_bn) # FIXME: This is constructing an X.509 SubjectPublicKeyInfo and is very # inefficient obj = OpenSSL::ASN1.Sequence([ OpenSSL::ASN1.Sequence([ OpenSSL::ASN1.ObjectId("dhKeyAgreement"), OpenSSL::ASN1.Sequence([ OpenSSL::ASN1.Integer(p), OpenSSL::ASN1.Integer(g), ]), ]), OpenSSL::ASN1.BitString(OpenSSL::ASN1.Integer(pub_bn).to_der), ]) derive(OpenSSL::PKey.read(obj.to_der)) end # :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. # # Deprecated in version 3.0. This method is incompatible with # OpenSSL 3.0.0 or later. # # See also OpenSSL::PKey.generate_key. # # Example: # # DEPRECATED USAGE: This will not work on OpenSSL 3.0 or later # dh0 = OpenSSL::PKey::DH.new(2048) # dh = dh0.public_key # #public_key only copies the DH parameters (contrary to the name) # dh.generate_key! # puts dh.private? # => true # puts dh0.pub_key == dh.pub_key #=> false # # # With OpenSSL::PKey.generate_key # dh0 = OpenSSL::PKey::DH.new(2048) # dh = OpenSSL::PKey.generate_key(dh0) # puts dh0.pub_key == dh.pub_key #=> false def generate_key! if OpenSSL::OPENSSL_VERSION_NUMBER >= 0x30000000 raise PKeyError, "OpenSSL::PKey::DH is immutable on OpenSSL 3.0; " \ "use OpenSSL::PKey.generate_key instead" end unless priv_key tmp = OpenSSL::PKey.generate_key(self) set_key(tmp.pub_key, tmp.priv_key) end self end class << self # :call-seq: # DH.generate(size, generator = 2) -> dh # # Creates a new DH instance from scratch by generating random parameters # and a key pair. # # See also OpenSSL::PKey.generate_parameters and # OpenSSL::PKey.generate_key. # # +size+:: # The desired key size in bits. # +generator+:: # The generator. def generate(size, generator = 2, &blk) dhparams = OpenSSL::PKey.generate_parameters("DH", { "dh_paramgen_prime_len" => size, "dh_paramgen_generator" => generator, }, &blk) OpenSSL::PKey.generate_key(dhparams) end # Handle DH.new(size, generator) form here; new(str) and new() forms # are handled by #initialize def new(*args, &blk) # :nodoc: if args[0].is_a?(Integer) generate(*args, &blk) else super end end end end # Alias of PKeyError. Before version 4.0.0, this was a subclass of PKeyError. DSAError = PKeyError class DSA include OpenSSL::Marshal # :call-seq: # dsa.public_key -> dsanew # # Returns a new DSA instance that carries just the \DSA parameters and the # public key. # # This method is provided for backwards compatibility. In most cases, there # is no need to call this method. # # For the purpose of serializing the public key, to PEM or DER encoding of # X.509 SubjectPublicKeyInfo format, check PKey#public_to_pem and # PKey#public_to_der. def public_key OpenSSL::PKey.read(public_to_der) end # :call-seq: # dsa.params -> hash # # Stores all parameters of key to a Hash. # # The hash has keys 'p', 'q', 'g', 'pub_key', and 'priv_key'. def params %w{p q g pub_key priv_key}.map { |name| [name, send(name)] }.to_h end class << self # :call-seq: # DSA.generate(size) -> dsa # # Creates a new DSA instance by generating a private/public key pair # from scratch. # # See also OpenSSL::PKey.generate_parameters and # OpenSSL::PKey.generate_key. # # +size+:: # The desired key size in bits. def generate(size, &blk) # FIPS 186-4 specifies four (L,N) pairs: (1024,160), (2048,224), # (2048,256), and (3072,256). # # q size is derived here with compatibility with # DSA_generator_parameters_ex() which previous versions of ruby/openssl # used to call. qsize = size >= 2048 ? 256 : 160 dsaparams = OpenSSL::PKey.generate_parameters("DSA", { "dsa_paramgen_bits" => size, "dsa_paramgen_q_bits" => qsize, }, &blk) OpenSSL::PKey.generate_key(dsaparams) end # Handle DSA.new(size) form here; new(str) and new() forms # are handled by #initialize def new(*args, &blk) # :nodoc: if args[0].is_a?(Integer) generate(*args, &blk) else super end end end # :call-seq: # dsa.syssign(string) -> string # # 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. # # Deprecated in version 3.0. # Consider using PKey::PKey#sign_raw and PKey::PKey#verify_raw instead. # # +string+:: # A message digest of the original input data to be signed. # # Example: # dsa = OpenSSL::PKey::DSA.new(2048) # doc = "Sign me" # digest = OpenSSL::Digest.digest('SHA1', doc) # # # With legacy #syssign and #sysverify: # sig = dsa.syssign(digest) # p dsa.sysverify(digest, sig) #=> true # # # With #sign_raw and #verify_raw: # sig = dsa.sign_raw(nil, digest) # p dsa.verify_raw(nil, sig, digest) #=> true def syssign(string) q or raise PKeyError, "incomplete DSA" private? or raise PKeyError, "Private DSA key needed!" sign_raw(nil, string) end # :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. # # Deprecated in version 3.0. # Consider using PKey::PKey#sign_raw and PKey::PKey#verify_raw instead. # # +digest+:: # A message digest of the original input data to be signed. # +sig+:: # A \DSA signature value. def sysverify(digest, sig) verify_raw(nil, sig, digest) end end if defined?(EC) # Alias of PKeyError. Before version 4.0.0, this was a subclass of PKeyError. ECError = PKeyError class EC include OpenSSL::Marshal # :call-seq: # key.dsa_sign_asn1(data) -> String # # Deprecated in version 3.0. # Consider using PKey::PKey#sign_raw and PKey::PKey#verify_raw instead. def dsa_sign_asn1(data) sign_raw(nil, data) end # :call-seq: # key.dsa_verify_asn1(data, sig) -> true | false # # Deprecated in version 3.0. # Consider using PKey::PKey#sign_raw and PKey::PKey#verify_raw instead. def dsa_verify_asn1(data, sig) verify_raw(nil, sig, data) end # :call-seq: # ec.dh_compute_key(pubkey) -> string # # Derives a shared secret by ECDH. _pubkey_ must be an instance of # OpenSSL::PKey::EC::Point and must belong to the same group. # # This method is provided for backwards compatibility, and calls #derive # internally. def dh_compute_key(pubkey) obj = OpenSSL::ASN1.Sequence([ OpenSSL::ASN1.Sequence([ OpenSSL::ASN1.ObjectId("id-ecPublicKey"), group.to_der, ]), OpenSSL::ASN1.BitString(pubkey.to_octet_string(:uncompressed)), ]) derive(OpenSSL::PKey.read(obj.to_der)) end end class EC::Point # :call-seq: # point.to_bn([conversion_form]) -> OpenSSL::BN # # Returns the octet string representation of the EC point as an instance of # OpenSSL::BN. # # If _conversion_form_ is not given, the _point_conversion_form_ attribute # set to the group is used. # # See #to_octet_string for more information. def to_bn(conversion_form = group.point_conversion_form) OpenSSL::BN.new(to_octet_string(conversion_form), 2) end end end # Alias of PKeyError. Before version 4.0.0, this was a subclass of PKeyError. RSAError = PKeyError class RSA include OpenSSL::Marshal # :call-seq: # rsa.public_key -> rsanew # # Returns a new RSA instance that carries just the public key components. # # This method is provided for backwards compatibility. In most cases, there # is no need to call this method. # # For the purpose of serializing the public key, to PEM or DER encoding of # X.509 SubjectPublicKeyInfo format, check PKey#public_to_pem and # PKey#public_to_der. def public_key OpenSSL::PKey.read(public_to_der) end # :call-seq: # rsa.params -> hash # # Stores all parameters of key to a Hash. # # The hash has keys 'n', 'e', 'd', 'p', 'q', 'dmp1', 'dmq1', and 'iqmp'. def params %w{n e d p q dmp1 dmq1 iqmp}.map { |name| [name, send(name)] }.to_h end class << self # :call-seq: # RSA.generate(size, exponent = 65537) -> RSA # # Generates an \RSA keypair. # # See also OpenSSL::PKey.generate_key. # # +size+:: # The desired key size in bits. # +exponent+:: # An odd Integer, normally 3, 17, or 65537. def generate(size, exp = 0x10001, &blk) OpenSSL::PKey.generate_key("RSA", { "rsa_keygen_bits" => size, "rsa_keygen_pubexp" => exp, }, &blk) end # Handle RSA.new(size, exponent) form here; new(str) and new() forms # are handled by #initialize def new(*args, &blk) # :nodoc: if args[0].is_a?(Integer) generate(*args, &blk) else super end end end # :call-seq: # rsa.private_encrypt(string) -> String # rsa.private_encrypt(string, padding) -> String # # Encrypt +string+ with the private key. +padding+ defaults to # PKCS1_PADDING, which is known to be insecure but is kept for backwards # compatibility. The encrypted string output can be decrypted using # #public_decrypt. # # Deprecated in version 3.0. # Consider using PKey::PKey#sign_raw and PKey::PKey#verify_raw, and # PKey::PKey#verify_recover instead. def private_encrypt(string, padding = PKCS1_PADDING) n or raise PKeyError, "incomplete RSA" private? or raise PKeyError, "private key needed." sign_raw(nil, string, { "rsa_padding_mode" => translate_padding_mode(padding), }) end # :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 which is known to be # insecure but is kept for backwards compatibility. # # Deprecated in version 3.0. # Consider using PKey::PKey#sign_raw and PKey::PKey#verify_raw, and # PKey::PKey#verify_recover instead. def public_decrypt(string, padding = PKCS1_PADDING) n or raise PKeyError, "incomplete RSA" verify_recover(nil, string, { "rsa_padding_mode" => translate_padding_mode(padding), }) end # :call-seq: # rsa.public_encrypt(string) -> String # rsa.public_encrypt(string, padding) -> String # # Encrypt +string+ with the public key. +padding+ defaults to # PKCS1_PADDING, which is known to be insecure but is kept for backwards # compatibility. The encrypted string output can be decrypted using # #private_decrypt. # # Deprecated in version 3.0. # Consider using PKey::PKey#encrypt and PKey::PKey#decrypt instead. def public_encrypt(data, padding = PKCS1_PADDING) n or raise PKeyError, "incomplete RSA" encrypt(data, { "rsa_padding_mode" => translate_padding_mode(padding), }) end # :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, which is known to be # insecure but is kept for backwards compatibility. # # Deprecated in version 3.0. # Consider using PKey::PKey#encrypt and PKey::PKey#decrypt instead. def private_decrypt(data, padding = PKCS1_PADDING) n or raise PKeyError, "incomplete RSA" private? or raise PKeyError, "private key needed." decrypt(data, { "rsa_padding_mode" => translate_padding_mode(padding), }) end PKCS1_PADDING = 1 SSLV23_PADDING = 2 NO_PADDING = 3 PKCS1_OAEP_PADDING = 4 private def translate_padding_mode(num) case num when PKCS1_PADDING "pkcs1" when SSLV23_PADDING "sslv23" when NO_PADDING "none" when PKCS1_OAEP_PADDING "oaep" else raise PKeyError, "unsupported padding mode" end end end end ================================================ FILE: lib/openssl/ssl.rb ================================================ # frozen_string_literal: true =begin = Info 'OpenSSL for Ruby 2' project Copyright (C) 2001 GOTOU YUUZOU All rights reserved. = Licence This program is licensed under the same licence as Ruby. (See the file 'COPYING'.) =end require "openssl/buffering" if defined?(OpenSSL::SSL) require "io/nonblock" require "ipaddr" require "socket" module OpenSSL module SSL class SSLContext DEFAULT_PARAMS = { # :nodoc: :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 opts }.call } if !OpenSSL::OPENSSL_VERSION.start_with?("OpenSSL") DEFAULT_PARAMS.merge!( min_version: OpenSSL::SSL::TLS1_VERSION, 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(":").freeze, ) end DEFAULT_PARAMS.freeze DEFAULT_CERT_STORE = OpenSSL::X509::Store.new # :nodoc: DEFAULT_CERT_STORE.set_default_paths # 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 # call-seq: # SSLContext.new -> ctx # SSLContext.new(:TLSv1) -> ctx # SSLContext.new("SSLv23") -> ctx # # Creates a new SSL context. # # If an argument is given, #ssl_version= is called with the value. Note # that this form is deprecated. New applications should use #min_version= # and #max_version= as necessary. def initialize(version = nil) self.ssl_version = version if version self.verify_mode = OpenSSL::SSL::VERIFY_NONE self.verify_hostname = false 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) self.options |= params.delete(:options) # set before min_version/max_version 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 if not defined?(Ractor) or Ractor.current == Ractor.main self.cert_store = DEFAULT_CERT_STORE else self.cert_store = Ractor.current[:__openssl_default_store__] ||= OpenSSL::X509::Store.new.tap { |store| store.set_default_paths } end end end return params end # call-seq: # ctx.ssl_version = :TLSv1 # ctx.ssl_version = "SSLv23" # # Sets the SSL/TLS protocol version for the context. This forces # connections to use only the specified protocol version. This is # deprecated and only provided for backwards compatibility. Use # #min_version= and #max_version= instead. # # === History # As the name hints, this used to call the SSL_CTX_set_ssl_version() # function which sets the SSL method used for connections created from # the context. As of Ruby/OpenSSL 2.1, this accessor method is # implemented to call #min_version= and #max_version= instead. def ssl_version=(meth) meth = meth.to_s if meth.is_a?(Symbol) if /(?_client|_server)\z/ =~ meth meth = $` if $VERBOSE warn "#{caller(1, 1)[0]}: method type #{type.inspect} is ignored" end end version = METHODS_MAP[meth.intern] or raise ArgumentError, "unknown SSL method `%s'" % meth self.min_version = self.max_version = version end METHODS_MAP = { SSLv23: 0, SSLv2: OpenSSL::SSL::SSL2_VERSION, SSLv3: OpenSSL::SSL::SSL3_VERSION, TLSv1: OpenSSL::SSL::TLS1_VERSION, TLSv1_1: OpenSSL::SSL::TLS1_1_VERSION, TLSv1_2: OpenSSL::SSL::TLS1_2_VERSION, }.freeze private_constant :METHODS_MAP # The list of available SSL/TLS methods. This constant is only provided # for backwards compatibility. METHODS = METHODS_MAP.flat_map { |name,| [name, :"#{name}_client", :"#{name}_server"] }.freeze deprecate_constant :METHODS end module SocketForwarder # The file descriptor for the socket. def fileno to_io.fileno end def addr to_io.addr end def peeraddr to_io.peeraddr end def local_address to_io.local_address end def remote_address to_io.remote_address 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 def close_on_exec=(value) to_io.close_on_exec = value end def close_on_exec? to_io.close_on_exec? end def wait(*args) to_io.wait(*args) end def wait_readable(*args) to_io.wait_readable(*args) end def wait_writable(*args) to_io.wait_writable(*args) end if IO.method_defined?(:timeout) def timeout to_io.timeout end def timeout=(value) to_io.timeout=(value) end 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 if san.value.size == 4 || san.value.size == 16 begin return true if san.value == IPAddr.new(hostname).hton rescue IPAddr::InvalidAddressError end 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 attr_reader :hostname # 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 # Close the stream for reading. # This method is ignored by OpenSSL as there is no reasonable way to # implement it, but exists for compatibility with IO. def close_read # Unsupported and ignored. # Just don't read any more. end # Closes the stream for writing. The behavior of this method depends on # the version of OpenSSL and the TLS protocol in use. # # - Sends a 'close_notify' alert to the peer. # - Does not wait for the peer's 'close_notify' alert in response. # # In TLS 1.2 and earlier: # - On receipt of a 'close_notify' alert, responds with a 'close_notify' # alert of its own and close down the connection immediately, # discarding any pending writes. # # Therefore, on TLS 1.2, this method will cause the connection to be # completely shut down. On TLS 1.3, the connection will remain open for # reading only. def close_write stop 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 session_new_cb @context.session_new_cb end def session_get_cb @context.session_get_cb end class << self # call-seq: # open(remote_host, remote_port, local_host=nil, local_port=nil, context: nil) # # Creates a new instance of SSLSocket. # _remote\_host_ and _remote\_port_ are used to open TCPSocket. # If _local\_host_ and _local\_port_ are specified, # then those parameters are used on the local end to establish the connection. # If _context_ is provided, # the SSL Sockets initial params will be taken from the context. # # === Examples # # sock = OpenSSL::SSL::SSLSocket.open('localhost', 443) # sock.connect # Initiates a connection to localhost:443 # # with SSLContext: # # ctx = OpenSSL::SSL::SSLContext.new # sock = OpenSSL::SSL::SSLSocket.open('localhost', 443, context: ctx) # sock.connect # Initiates a connection to localhost:443 with SSLContext def open(remote_host, remote_port, local_host=nil, local_port=nil, context: nil) sock = ::TCPSocket.open(remote_host, remote_port, local_host, local_port) if context.nil? return OpenSSL::SSL::SSLSocket.new(sock) else return OpenSSL::SSL::SSLSocket.new(sock, context) end end 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).unpack1('H*') @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=Socket::SOMAXCONN) @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 end ================================================ FILE: lib/openssl/version.rb ================================================ # frozen_string_literal: true module OpenSSL # The version string of Ruby/OpenSSL. VERSION = "4.0.1" end ================================================ FILE: lib/openssl/x509.rb ================================================ # frozen_string_literal: true #-- # = Ruby-space definitions that completes C-space funcs for X509 and subclasses # # = Info # 'OpenSSL for Ruby 2' project # Copyright (C) 2002 Michal Rokos # All rights reserved. # # = Licence # This program is licensed under the same licence as Ruby. # (See the file 'COPYING'.) #++ require_relative 'marshal' 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 include OpenSSL::Marshal def ==(other) return false unless Extension === other to_der == other.to_der end 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 module Helpers def find_extension(oid) extensions.find { |e| e.oid == oid } end end module SubjectKeyIdentifier include Helpers # Get the subject's key identifier from the subjectKeyIdentifier # exteension, as described in RFC5280 Section 4.2.1.2. # # Returns the binary String key identifier or nil or raises # ASN1::ASN1Error. def subject_key_identifier ext = find_extension("subjectKeyIdentifier") return nil if ext.nil? ski_asn1 = ASN1.decode(ext.value_der) if ext.critical? || ski_asn1.tag_class != :UNIVERSAL || ski_asn1.tag != ASN1::OCTET_STRING raise ASN1::ASN1Error, "invalid extension" end ski_asn1.value end end module AuthorityKeyIdentifier include Helpers # Get the issuing certificate's key identifier from the # authorityKeyIdentifier extension, as described in RFC5280 # Section 4.2.1.1 # # Returns the binary String keyIdentifier or nil or raises # ASN1::ASN1Error. def authority_key_identifier ext = find_extension("authorityKeyIdentifier") return nil if ext.nil? aki_asn1 = ASN1.decode(ext.value_der) if ext.critical? || aki_asn1.tag_class != :UNIVERSAL || aki_asn1.tag != ASN1::SEQUENCE raise ASN1::ASN1Error, "invalid extension" end key_id = aki_asn1.value.find do |v| v.tag_class == :CONTEXT_SPECIFIC && v.tag == 0 end key_id.nil? ? nil : key_id.value end end module CRLDistributionPoints include Helpers # Get the distributionPoint fullName URI from the certificate's CRL # distribution points extension, as described in RFC 5280 Section # 4.2.1.13. # # Returns an array of strings or nil or raises ASN1::ASN1Error. def crl_uris ext = find_extension("crlDistributionPoints") return nil if ext.nil? cdp_asn1 = ASN1.decode(ext.value_der) if cdp_asn1.tag_class != :UNIVERSAL || cdp_asn1.tag != ASN1::SEQUENCE raise ASN1::ASN1Error, "invalid extension" end crl_uris = cdp_asn1.flat_map do |crl_distribution_point| distribution_point = crl_distribution_point.value.find do |v| v.tag_class == :CONTEXT_SPECIFIC && v.tag == 0 end full_name = distribution_point&.value&.find do |v| v.tag_class == :CONTEXT_SPECIFIC && v.tag == 0 end full_name&.value&.select do |v| v.tag_class == :CONTEXT_SPECIFIC && v.tag == 6 # uniformResourceIdentifier end end crl_uris.empty? ? nil : crl_uris.map(&:value) end end module AuthorityInfoAccess include Helpers # Get the information and services for the issuer from the certificate's # authority information access extension exteension, as described in RFC5280 # Section 4.2.2.1. # # Returns an array of strings or nil or raises ASN1::ASN1Error. def ca_issuer_uris aia_asn1 = parse_aia_asn1 return nil if aia_asn1.nil? ca_issuer = aia_asn1.value.select do |authority_info_access| authority_info_access.value.first.value == "caIssuers" end ca_issuer&.map(&:value)&.map(&:last)&.map(&:value) end # Get the URIs for OCSP from the certificate's authority information access # extension exteension, as described in RFC5280 Section 4.2.2.1. # # Returns an array of strings or nil or raises ASN1::ASN1Error. def ocsp_uris aia_asn1 = parse_aia_asn1 return nil if aia_asn1.nil? ocsp = aia_asn1.value.select do |authority_info_access| authority_info_access.value.first.value == "OCSP" end ocsp&.map(&:value)&.map(&:last)&.map(&:value) end private def parse_aia_asn1 ext = find_extension("authorityInfoAccess") return nil if ext.nil? aia_asn1 = ASN1.decode(ext.value_der) if ext.critical? || aia_asn1.tag_class != :UNIVERSAL || aia_asn1.tag != ASN1::SEQUENCE raise ASN1::ASN1Error, "invalid extension" end aia_asn1 end end end class Name include OpenSSL::Marshal 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 # Parses the UTF-8 string representation of a distinguished name, # according to RFC 2253. # # See also #to_utf8 for the opposite operation. def parse_rfc2253(str, template=OBJECT_TYPE_TEMPLATE) ary = OpenSSL::X509::Name::RFC2253DN.scan(str) self.new(ary, template) end # Parses the string representation of a distinguished name. Two # different forms are supported: # # - \OpenSSL format (X509_NAME_oneline()) used by # #to_s. For example: /DC=com/DC=example/CN=nobody # - \OpenSSL format (X509_NAME_print()) # used by #to_s(OpenSSL::X509::Name::COMPAT). For example: # DC=com, DC=example, CN=nobody # # Neither of them is standardized and has quirks and inconsistencies # in handling of escaped characters or multi-valued RDNs. # # Use of this method is discouraged in new applications. See # Name.parse_rfc2253 and #to_utf8 for the alternative. def parse_openssl(str, template=OBJECT_TYPE_TEMPLATE) if str.start_with?("/") # /A=B/C=D format ary = str[1..-1].split("/").map { |i| i.split("=", 2) } else # Comma-separated ary = str.split(",").map { |i| i.strip.split("=", 2) } end 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 Attribute include OpenSSL::Marshal def ==(other) return false unless Attribute === other to_der == other.to_der end end class StoreContext def cleanup warn "(#{caller.first}) OpenSSL::X509::StoreContext#cleanup is deprecated with no replacement" if $VERBOSE end end class Certificate include OpenSSL::Marshal include Extension::SubjectKeyIdentifier include Extension::AuthorityKeyIdentifier include Extension::CRLDistributionPoints include Extension::AuthorityInfoAccess def inspect "#<#{self.class}: " \ "subject=#{subject.inspect}, " \ "issuer=#{issuer.inspect}, " \ "serial=#{serial.inspect}, " \ "not_before=#{not_before.inspect rescue "(error)"}, " \ "not_after=#{not_after.inspect rescue "(error)"}>" end 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 def self.load_file(path) load(File.binread(path)) end end class CRL include OpenSSL::Marshal include Extension::AuthorityKeyIdentifier def ==(other) return false unless CRL === other to_der == other.to_der end end class Revoked def ==(other) return false unless Revoked === other to_der == other.to_der end end class Request include OpenSSL::Marshal def ==(other) return false unless Request === other to_der == other.to_der end end end end ================================================ FILE: lib/openssl.rb ================================================ # frozen_string_literal: true =begin = Info 'OpenSSL for Ruby 2' project Copyright (C) 2002 Michal Rokos All rights reserved. = Licence This program is licensed under the same licence as Ruby. (See the file 'COPYING'.) =end require 'openssl.so' require_relative 'openssl/bn' require_relative 'openssl/cipher' require_relative 'openssl/digest' require_relative 'openssl/hmac' require_relative 'openssl/pkcs5' require_relative 'openssl/pkey' require_relative 'openssl/ssl' require_relative 'openssl/version' require_relative 'openssl/x509' module OpenSSL # :call-seq: # OpenSSL.secure_compare(string, string) -> true or false # # Constant time memory comparison. Inputs are hashed using SHA-256 to mask # the length of the secret. Returns +true+ if the strings are identical, # +false+ otherwise. # # This method is expensive due to the SHA-256 hashing. In most cases, where # the input lengths are known to be equal or are not sensitive, # OpenSSL.fixed_length_secure_compare should be used instead. def self.secure_compare(a, b) hashed_a = OpenSSL::Digest.digest('SHA256', a) hashed_b = OpenSSL::Digest.digest('SHA256', b) OpenSSL.fixed_length_secure_compare(hashed_a, hashed_b) && a == b end end ================================================ FILE: openssl.gemspec ================================================ Gem::Specification.new do |spec| spec.name = "openssl" spec.version = "4.0.1" spec.authors = ["Martin Bosslet", "SHIBATA Hiroshi", "Zachary Scott", "Kazuki Yamaguchi"] spec.email = ["ruby-core@ruby-lang.org"] spec.summary = %q{SSL/TLS and general-purpose cryptography for Ruby} spec.description = %q{OpenSSL for Ruby provides access to SSL/TLS and general-purpose cryptography based on the OpenSSL library.} spec.homepage = "https://github.com/ruby/openssl" spec.licenses = ["Ruby", "BSD-2-Clause"] if Gem::Platform === spec.platform and spec.platform =~ 'java' or RUBY_ENGINE == 'jruby' spec.platform = "java" spec.files = [] spec.add_runtime_dependency('jruby-openssl', '~> 0.14') else spec.files = Dir.glob(["lib/**/*.rb", "ext/**/*.{c,h,rb}", "*.md"], base: File.expand_path("..", __FILE__)) + ["BSDL", "COPYING"] spec.require_paths = ["lib"] spec.extensions = ["ext/openssl/extconf.rb"] end spec.extra_rdoc_files = Dir["*.md"] spec.rdoc_options = ["--main", "README.md"] spec.required_ruby_version = ">= 2.7.0" spec.metadata["msys2_mingw_dependencies"] = "openssl" end ================================================ FILE: sample/c_rehash.rb ================================================ #!/usr/bin/env ruby require 'openssl' 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("%08x", name.hash) end def fingerprint(der) OpenSSL::Digest.hexdigest('MD5', 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 ================================================ FILE: sample/cert2text.rb ================================================ #!/usr/bin/env ruby require 'openssl' def cert2text(cert_str) [ OpenSSL::X509::Certificate, OpenSSL::X509::CRL, OpenSSL::X509::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 ================================================ FILE: sample/certstore.rb ================================================ require 'c_rehash' require 'crlstore' class CertStore 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 = OpenSSL::X509::Store.new @self_signed_ca = @other_ca = @ee = @crl = nil # Uncomment this line to let OpenSSL to check CRL for each certs. # @x509store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK | OpenSSL::X509::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 ================================================ FILE: sample/cipher.rb ================================================ #!/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) ================================================ FILE: sample/crlstore.rb ================================================ 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 ================================================ FILE: sample/echo_cli.rb ================================================ #!/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.read(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 ================================================ FILE: sample/echo_svr.rb ================================================ #!/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.read(File::read(key_file)) else key = OpenSSL::PKey::RSA.new(2048){ 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 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, "SHA1") 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 ================================================ FILE: sample/gen_csr.rb ================================================ #!/usr/bin/env ruby require 'optparse' require 'openssl' def usage myname = File::basename($0) $stderr.puts < (tag, klass) { encode_decode_test tag.chr + B(%w{ 00 }), klass.new(B(%w{})) encode_decode_test tag.chr + B(%w{ 02 00 01 }), klass.new(B(%w{ 00 01 })) } test.(4, OpenSSL::ASN1::OctetString) test.(12, OpenSSL::ASN1::UTF8String) test.(18, OpenSSL::ASN1::NumericString) test.(19, OpenSSL::ASN1::PrintableString) test.(20, OpenSSL::ASN1::T61String) test.(21, OpenSSL::ASN1::VideotexString) test.(22, OpenSSL::ASN1::IA5String) test.(25, OpenSSL::ASN1::GraphicString) test.(26, OpenSSL::ASN1::ISO64String) test.(27, OpenSSL::ASN1::GeneralString) test.(28, OpenSSL::ASN1::UniversalString) test.(30, OpenSSL::ASN1::BMPString) end def test_null encode_decode_test B(%w{ 05 00 }), OpenSSL::ASN1::Null.new(nil) assert_raise(OpenSSL::ASN1::ASN1Error) { OpenSSL::ASN1.decode(B(%w{ 05 01 00 })) } end def test_object_identifier obj = encode_decode_test B(%w{ 06 01 00 }), OpenSSL::ASN1::ObjectId.new("0.0".b) assert_equal "0.0", obj.oid assert_nil obj.sn assert_nil obj.ln assert_equal obj.oid, obj.value encode_decode_test B(%w{ 06 01 28 }), OpenSSL::ASN1::ObjectId.new("1.0".b) encode_decode_test B(%w{ 06 03 88 37 03 }), OpenSSL::ASN1::ObjectId.new("2.999.3".b) encode_decode_test B(%w{ 06 05 2A 22 83 BB 55 }), OpenSSL::ASN1::ObjectId.new("1.2.34.56789".b) obj = encode_decode_test B(%w{ 06 09 60 86 48 01 65 03 04 02 01 }), OpenSSL::ASN1::ObjectId.new("sha256") assert_equal "2.16.840.1.101.3.4.2.1", obj.oid assert_equal "SHA256", obj.sn assert_equal "sha256", obj.ln assert_equal obj.sn, obj.value assert_raise(OpenSSL::ASN1::ASN1Error) { OpenSSL::ASN1.decode(B(%w{ 06 00 })) } assert_raise(OpenSSL::ASN1::ASN1Error) { OpenSSL::ASN1.decode(B(%w{ 06 01 80 })) } assert_raise(OpenSSL::ASN1::ASN1Error) { OpenSSL::ASN1::ObjectId.new("3.0".b).to_der } assert_raise(OpenSSL::ASN1::ASN1Error) { OpenSSL::ASN1::ObjectId.new("0.40".b).to_der } oid = (0...100).to_a.join(".").b obj = OpenSSL::ASN1::ObjectId.new(oid) assert_equal oid, obj.oid end def test_object_identifier_equality aki = [ OpenSSL::ASN1::ObjectId.new("authorityKeyIdentifier"), OpenSSL::ASN1::ObjectId.new("X509v3 Authority Key Identifier"), OpenSSL::ASN1::ObjectId.new("2.5.29.35") ] ski = [ OpenSSL::ASN1::ObjectId.new("subjectKeyIdentifier"), OpenSSL::ASN1::ObjectId.new("X509v3 Subject Key Identifier"), OpenSSL::ASN1::ObjectId.new("2.5.29.14") ] aki.each do |a| aki.each do |b| assert_equal true, a == b end ski.each do |b| assert_equal false, a == b end end obj1 = OpenSSL::ASN1::ObjectId.new("1.2.34.56789.10") obj2 = OpenSSL::ASN1::ObjectId.new("1.2.34.56789.10") obj3 = OpenSSL::ASN1::ObjectId.new("1.2.34.56789.11") omit "OID 1.2.34.56789.10 is registered" if obj1.sn assert_equal true, obj1 == obj2 assert_equal false, obj1 == obj3 assert_equal false, OpenSSL::ASN1::ObjectId.new("authorityKeyIdentifier") == nil end def test_sequence encode_decode_test B(%w{ 30 00 }), OpenSSL::ASN1::Sequence.new([]) encode_decode_test B(%w{ 30 07 05 00 30 00 04 01 00 }), OpenSSL::ASN1::Sequence.new([ OpenSSL::ASN1::Null.new(nil), OpenSSL::ASN1::Sequence.new([]), OpenSSL::ASN1::OctetString.new(B(%w{ 00 })) ]) expected = OpenSSL::ASN1::Sequence.new([OpenSSL::ASN1::OctetString.new(B(%w{ 00 }))]) expected.indefinite_length = true encode_decode_test B(%w{ 30 80 04 01 00 00 00 }), expected # OpenSSL::ASN1::EndOfContent can only be at the end obj = OpenSSL::ASN1::Sequence.new([ OpenSSL::ASN1::EndOfContent.new, OpenSSL::ASN1::OctetString.new(B(%w{ 00 })), OpenSSL::ASN1::EndOfContent.new, ]) obj.indefinite_length = true assert_raise(OpenSSL::ASN1::ASN1Error) { obj.to_der } # The last EOC in value is ignored if indefinite length form is used expected = OpenSSL::ASN1::Sequence.new([ OpenSSL::ASN1::OctetString.new(B(%w{ 00 })), OpenSSL::ASN1::EndOfContent.new ]) expected.indefinite_length = true encode_test B(%w{ 30 80 04 01 00 00 00 }), expected # Missing EOC at the end of contents octets assert_raise(OpenSSL::ASN1::ASN1Error) { OpenSSL::ASN1.decode(B(%w{ 30 80 01 01 FF })) } end def test_set encode_decode_test B(%w{ 31 00 }), OpenSSL::ASN1::Set.new([]) encode_decode_test B(%w{ 31 07 05 00 30 00 04 01 00 }), OpenSSL::ASN1::Set.new([ OpenSSL::ASN1::Null.new(nil), OpenSSL::ASN1::Sequence.new([]), OpenSSL::ASN1::OctetString.new(B(%w{ 00 })) ]) expected = OpenSSL::ASN1::Set.new([OpenSSL::ASN1::OctetString.new(B(%w{ 00 }))]) expected.indefinite_length = true encode_decode_test B(%w{ 31 80 04 01 00 00 00 }), expected end def test_utctime encode_decode_test B(%w{ 17 0D }) + "160908234339Z".b, OpenSSL::ASN1::UTCTime.new(Time.utc(2016, 9, 8, 23, 43, 39)) # 1950-2049 range is assumed to match RFC 5280's expectation encode_decode_test B(%w{ 17 0D }) + "490908234339Z".b, OpenSSL::ASN1::UTCTime.new(Time.utc(2049, 9, 8, 23, 43, 39)) encode_decode_test B(%w{ 17 0D }) + "500908234339Z".b, OpenSSL::ASN1::UTCTime.new(Time.utc(1950, 9, 8, 23, 43, 39)) assert_raise(OpenSSL::ASN1::ASN1Error) { OpenSSL::ASN1::UTCTime.new(Time.new(2049, 12, 31, 23, 0, 0, "-04:00")).to_der } # UTC offset (BER): ASN1_TIME_to_tm() may or may not support it # decode_test B(%w{ 17 11 }) + "500908234339+0930".b, # OpenSSL::ASN1::UTCTime.new(Time.new(1950, 9, 8, 23, 43, 39, "+09:30")) # decode_test B(%w{ 17 0F }) + "5009082343-0930".b, # OpenSSL::ASN1::UTCTime.new(Time.new(1950, 9, 8, 23, 43, 0, "-09:30")) # Seconds is omitted (BER) # decode_test B(%w{ 18 0D }) + "201612081934Z".b, # OpenSSL::ASN1::GeneralizedTime.new(Time.utc(2016, 12, 8, 19, 34, 0)) # Fractional seconds is not allowed in UTCTime assert_raise(OpenSSL::ASN1::ASN1Error) { OpenSSL::ASN1.decode(B(%w{ 17 0F }) + "160908234339.5Z".b) } # Missing "Z" assert_raise(OpenSSL::ASN1::ASN1Error) { OpenSSL::ASN1.decode(B(%w{ 17 0C }) + "500908234339".b) } assert_raise(OpenSSL::ASN1::ASN1Error) { OpenSSL::ASN1.decode(B(%w{ 17 0D }) + "500908234339Y".b) } end def test_generalizedtime encode_decode_test B(%w{ 18 0F }) + "20161208193429Z".b, OpenSSL::ASN1::GeneralizedTime.new(Time.utc(2016, 12, 8, 19, 34, 29)) encode_decode_test B(%w{ 18 0F }) + "99990908234339Z".b, OpenSSL::ASN1::GeneralizedTime.new(Time.utc(9999, 9, 8, 23, 43, 39)) # Fractional seconds (DER). Not supported by ASN1_TIME_to_tm() # because struct tm cannot store it. # encode_decode_test B(%w{ 18 11 }) + "20161208193439.5Z".b, # OpenSSL::ASN1::GeneralizedTime.new(Time.utc(2016, 12, 8, 19, 34, 39.5)) # UTC offset (BER): ASN1_TIME_to_tm() may or may not support it # decode_test B(%w{ 18 13 }) + "20161208193439+0930".b, # OpenSSL::ASN1::GeneralizedTime.new(Time.new(2016, 12, 8, 19, 34, 39, "+09:30")) # decode_test B(%w{ 18 11 }) + "201612081934-0930".b, # OpenSSL::ASN1::GeneralizedTime.new(Time.new(2016, 12, 8, 19, 34, 0, "-09:30")) # decode_test B(%w{ 18 11 }) + "201612081934-09".b, # OpenSSL::ASN1::GeneralizedTime.new(Time.new(2016, 12, 8, 19, 34, 0, "-09:00")) # Minutes and seconds are omitted (BER) # decode_test B(%w{ 18 0B }) + "2016120819Z".b, # OpenSSL::ASN1::GeneralizedTime.new(Time.utc(2016, 12, 8, 19, 0, 0)) # Fractional hours (BER) # decode_test B(%w{ 18 0D }) + "2016120819.5Z".b, # OpenSSL::ASN1::GeneralizedTime.new(Time.utc(2016, 12, 8, 19, 30, 0)) # Fractional hours with "," as the decimal separator (BER) # decode_test B(%w{ 18 0D }) + "2016120819,5Z".b, # OpenSSL::ASN1::GeneralizedTime.new(Time.utc(2016, 12, 8, 19, 30, 0)) # Seconds is omitted (BER) # decode_test B(%w{ 18 0D }) + "201612081934Z".b, # OpenSSL::ASN1::GeneralizedTime.new(Time.utc(2016, 12, 8, 19, 34, 0)) # Fractional minutes (BER) # decode_test B(%w{ 18 0F }) + "201612081934.5Z".b, # OpenSSL::ASN1::GeneralizedTime.new(Time.utc(2016, 12, 8, 19, 34, 30)) # Missing "Z" assert_raise(OpenSSL::ASN1::ASN1Error) { OpenSSL::ASN1.decode(B(%w{ 18 0F }) + "20161208193429Y".b) } # Encoding year out of range assert_raise(OpenSSL::ASN1::ASN1Error) { OpenSSL::ASN1::GeneralizedTime.new(Time.utc(10000, 9, 8, 23, 43, 39)).to_der } end def test_basic_asn1data encode_test B(%w{ 00 00 }), OpenSSL::ASN1::ASN1Data.new(B(%w{}), 0, :UNIVERSAL) encode_test B(%w{ 01 00 }), OpenSSL::ASN1::ASN1Data.new(B(%w{}), 1, :UNIVERSAL) encode_decode_test B(%w{ 41 00 }), OpenSSL::ASN1::ASN1Data.new(B(%w{}), 1, :APPLICATION) encode_decode_test B(%w{ 81 00 }), OpenSSL::ASN1::ASN1Data.new(B(%w{}), 1, :CONTEXT_SPECIFIC) encode_decode_test B(%w{ C1 00 }), OpenSSL::ASN1::ASN1Data.new(B(%w{}), 1, :PRIVATE) encode_decode_test B(%w{ 1F 20 00 }), OpenSSL::ASN1::ASN1Data.new(B(%w{}), 32, :UNIVERSAL) encode_decode_test B(%w{ 9F C0 20 00 }), OpenSSL::ASN1::ASN1Data.new(B(%w{}), 8224, :CONTEXT_SPECIFIC) encode_decode_test B(%w{ 41 02 AB CD }), OpenSSL::ASN1::ASN1Data.new(B(%w{ AB CD }), 1, :APPLICATION) encode_decode_test B(%w{ 41 81 80 } + %w{ AB CD } * 64), OpenSSL::ASN1::ASN1Data.new(B(%w{ AB CD } * 64), 1, :APPLICATION) encode_decode_test B(%w{ 41 82 01 00 } + %w{ AB CD } * 128), OpenSSL::ASN1::ASN1Data.new(B(%w{ AB CD } * 128), 1, :APPLICATION) encode_decode_test B(%w{ 61 00 }), OpenSSL::ASN1::ASN1Data.new([], 1, :APPLICATION) obj = OpenSSL::ASN1::ASN1Data.new([OpenSSL::ASN1::ASN1Data.new(B(%w{ AB CD }), 2, :PRIVATE)], 1, :APPLICATION) obj.indefinite_length = true encode_decode_test B(%w{ 61 80 C2 02 AB CD 00 00 }), obj obj = OpenSSL::ASN1::ASN1Data.new([ OpenSSL::ASN1::ASN1Data.new(B(%w{ AB CD }), 2, :PRIVATE), OpenSSL::ASN1::EndOfContent.new ], 1, :APPLICATION) obj.indefinite_length = true encode_test B(%w{ 61 80 C2 02 AB CD 00 00 }), obj obj = OpenSSL::ASN1::ASN1Data.new(B(%w{ AB CD }), 1, :UNIVERSAL) obj.indefinite_length = true assert_raise(OpenSSL::ASN1::ASN1Error) { obj.to_der } end def test_basic_primitive encode_test B(%w{ 00 00 }), OpenSSL::ASN1::Primitive.new(B(%w{}), 0) encode_test B(%w{ 01 00 }), OpenSSL::ASN1::Primitive.new(B(%w{}), 1, nil, :UNIVERSAL) encode_test B(%w{ 81 00 }), OpenSSL::ASN1::Primitive.new(B(%w{}), 1, nil, :CONTEXT_SPECIFIC) encode_test B(%w{ 01 02 AB CD }), OpenSSL::ASN1::Primitive.new(B(%w{ AB CD }), 1) assert_raise(TypeError) { OpenSSL::ASN1::Primitive.new([], 1).to_der } prim = OpenSSL::ASN1::Integer.new(50) assert_equal false, prim.indefinite_length assert_not_respond_to prim, :indefinite_length= end def test_basic_constructed octet_string = OpenSSL::ASN1::OctetString.new(B(%w{ AB CD })) encode_test B(%w{ 20 00 }), OpenSSL::ASN1::Constructive.new([], 0) encode_test B(%w{ 21 00 }), OpenSSL::ASN1::Constructive.new([], 1, nil, :UNIVERSAL) encode_test B(%w{ A1 00 }), OpenSSL::ASN1::Constructive.new([], 1, nil, :CONTEXT_SPECIFIC) encode_test B(%w{ 21 04 04 02 AB CD }), OpenSSL::ASN1::Constructive.new([octet_string], 1) obj = OpenSSL::ASN1::Constructive.new([octet_string], 1) obj.indefinite_length = true encode_decode_test B(%w{ 21 80 04 02 AB CD 00 00 }), obj obj = OpenSSL::ASN1::Constructive.new([octet_string, OpenSSL::ASN1::EndOfContent.new], 1) obj.indefinite_length = true encode_test B(%w{ 21 80 04 02 AB CD 00 00 }), obj end def test_prim_explicit_tagging oct_str = OpenSSL::ASN1::OctetString.new("a", 0, :EXPLICIT) encode_test B(%w{ A0 03 04 01 61 }), oct_str oct_str2 = OpenSSL::ASN1::OctetString.new("a", 1, :EXPLICIT, :APPLICATION) encode_test B(%w{ 61 03 04 01 61 }), oct_str2 decoded = OpenSSL::ASN1.decode(oct_str2.to_der) assert_equal :APPLICATION, decoded.tag_class assert_equal 1, decoded.tag assert_equal 1, decoded.value.size inner = decoded.value[0] assert_equal OpenSSL::ASN1::OctetString, inner.class assert_equal B(%w{ 61 }), inner.value end def test_prim_implicit_tagging int = OpenSSL::ASN1::Integer.new(1, 0, :IMPLICIT) encode_test B(%w{ 80 01 01 }), int int2 = OpenSSL::ASN1::Integer.new(1, 1, :IMPLICIT, :APPLICATION) encode_test B(%w{ 41 01 01 }), int2 decoded = OpenSSL::ASN1.decode(int2.to_der) assert_equal :APPLICATION, decoded.tag_class assert_equal 1, decoded.tag assert_equal B(%w{ 01 }), decoded.value # Special behavior: Encoding universal types with non-default 'tag' # attribute and nil tagging method. int3 = OpenSSL::ASN1::Integer.new(1, 1) encode_test B(%w{ 01 01 01 }), int3 end def test_cons_explicit_tagging content = [ OpenSSL::ASN1::PrintableString.new('abc') ] seq = OpenSSL::ASN1::Sequence.new(content, 2, :EXPLICIT) encode_test B(%w{ A2 07 30 05 13 03 61 62 63 }), seq seq2 = OpenSSL::ASN1::Sequence.new(content, 3, :EXPLICIT, :APPLICATION) encode_test B(%w{ 63 07 30 05 13 03 61 62 63 }), seq2 content3 = [ OpenSSL::ASN1::PrintableString.new('abc'), OpenSSL::ASN1::EndOfContent.new() ] seq3 = OpenSSL::ASN1::Sequence.new(content3, 2, :EXPLICIT) seq3.indefinite_length = true encode_test B(%w{ A2 80 30 80 13 03 61 62 63 00 00 00 00 }), seq3 end def test_cons_implicit_tagging content = [ OpenSSL::ASN1::Null.new(nil) ] seq = OpenSSL::ASN1::Sequence.new(content, 1, :IMPLICIT) encode_test B(%w{ A1 02 05 00 }), seq seq2 = OpenSSL::ASN1::Sequence.new(content, 1, :IMPLICIT, :APPLICATION) encode_test B(%w{ 61 02 05 00 }), seq2 content3 = [ OpenSSL::ASN1::Null.new(nil), OpenSSL::ASN1::EndOfContent.new() ] seq3 = OpenSSL::ASN1::Sequence.new(content3, 1, :IMPLICIT) seq3.indefinite_length = true encode_test B(%w{ A1 80 05 00 00 00 }), seq3 # Special behavior: Encoding universal types with non-default 'tag' # attribute and nil tagging method. seq4 = OpenSSL::ASN1::Sequence.new([], 1) encode_test B(%w{ 21 00 }), seq4 end def test_octet_string_constructed_tagging octets = [ OpenSSL::ASN1::OctetString.new('aaa') ] cons = OpenSSL::ASN1::Constructive.new(octets, 0, :IMPLICIT) encode_test B(%w{ A0 05 04 03 61 61 61 }), cons octets = [ OpenSSL::ASN1::OctetString.new('aaa'), OpenSSL::ASN1::EndOfContent.new() ] cons = OpenSSL::ASN1::Constructive.new(octets, 0, :IMPLICIT) cons.indefinite_length = true encode_test B(%w{ A0 80 04 03 61 61 61 00 00 }), cons end def test_recursive_octet_string_indefinite_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.indefinite_length = true container2 = OpenSSL::ASN1::Constructive.new(octets_sub2, OpenSSL::ASN1::OCTET_STRING, nil, :UNIVERSAL) container2.indefinite_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.indefinite_length = true raw = B(%w{ 24 80 24 80 04 01 01 00 00 24 80 04 01 02 00 00 04 01 03 00 00 }) assert_equal(raw, cons.to_der) assert_equal(raw, OpenSSL::ASN1.decode(raw).to_der) end def test_recursive_octet_string_parse raw = B(%w{ 24 80 24 80 04 01 01 00 00 24 80 04 01 02 00 00 04 01 03 00 00 }) asn1 = OpenSSL::ASN1.decode(raw) assert_equal(OpenSSL::ASN1::Constructive, asn1.class) assert_universal(OpenSSL::ASN1::OCTET_STRING, asn1) assert_equal(true, asn1.indefinite_length) assert_equal(3, 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.indefinite_length) assert_equal(1, nested1.value.size) oct1 = nested1.value[0] assert_universal(OpenSSL::ASN1::OCTET_STRING, oct1) assert_equal(false, oct1.indefinite_length) nested2 = asn1.value[1] assert_equal(OpenSSL::ASN1::Constructive, nested2.class) assert_universal(OpenSSL::ASN1::OCTET_STRING, nested2) assert_equal(true, nested2.indefinite_length) assert_equal(1, nested2.value.size) oct2 = nested2.value[0] assert_universal(OpenSSL::ASN1::OCTET_STRING, oct2) assert_equal(false, oct2.indefinite_length) oct3 = asn1.value[2] assert_universal(OpenSSL::ASN1::OCTET_STRING, oct3) assert_equal(false, oct3.indefinite_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 # Very time consuming test. # def test_gc_stress # assert_ruby_status(['--disable-gems', '-eGC.stress=true', '-erequire "openssl.so"']) # end private def B(ary) [ary.join].pack("H*") end def assert_asn1_equal(a, b) assert_equal a.class, b.class assert_equal a.tag, b.tag assert_equal a.tag_class, b.tag_class assert_equal a.indefinite_length, b.indefinite_length assert_equal a.unused_bits, b.unused_bits if a.respond_to?(:unused_bits) case a.value when Array a.value.each_with_index { |ai, i| assert_asn1_equal ai, b.value[i] } else if OpenSSL::ASN1::ObjectId === a assert_equal a.oid, b.oid else assert_equal a.value, b.value end end assert_equal a.to_der, b.to_der end def encode_test(der, obj) assert_equal der, obj.to_der end def decode_test(der, obj) decoded = OpenSSL::ASN1.decode(der) assert_asn1_equal obj, decoded decoded end def encode_decode_test(der, obj) encode_test(der, obj) decode_test(der, obj) end 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 end ================================================ FILE: test/openssl/test_bn.rb ================================================ # coding: us-ascii # frozen_string_literal: true require_relative 'utils' if defined?(OpenSSL) class OpenSSL::TestBN < OpenSSL::TestCase def setup super @e1 = OpenSSL::BN.new(999.to_s(16), 16) # OpenSSL::BN.new(str, 16) must be most stable @e2 = OpenSSL::BN.new("-" + 999.to_s(16), 16) @e3 = OpenSSL::BN.new((2**107-1).to_s(16), 16) @e4 = OpenSSL::BN.new("-" + (2**107-1).to_s(16), 16) end def test_new assert_raise(ArgumentError) { OpenSSL::BN.new } assert_raise(ArgumentError) { OpenSSL::BN.new(nil) } assert_raise(ArgumentError) { OpenSSL::BN.new(nil, 2) } assert_equal(@e1, OpenSSL::BN.new("999")) assert_equal(@e1, OpenSSL::BN.new("999", 10)) assert_equal(@e1, OpenSSL::BN.new("\x03\xE7", 2)) assert_equal(@e1, OpenSSL::BN.new("\x00\x00\x00\x02\x03\xE7", 0)) assert_equal(@e2, OpenSSL::BN.new("-999")) assert_equal(@e2, OpenSSL::BN.new("-999", 10)) assert_equal(@e2, OpenSSL::BN.new("\x00\x00\x00\x02\x83\xE7", 0)) assert_equal(@e3, OpenSSL::BN.new((2**107-1).to_s)) assert_equal(@e3, OpenSSL::BN.new((2**107-1).to_s, 10)) assert_equal(@e3, OpenSSL::BN.new("\a\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 2)) assert_equal(@e3, OpenSSL::BN.new("\x00\x00\x00\x0E\a\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 0)) assert_equal(@e4, OpenSSL::BN.new("-" + (2**107-1).to_s)) assert_equal(@e4, OpenSSL::BN.new("-" + (2**107-1).to_s, 10)) assert_equal(@e4, OpenSSL::BN.new("\x00\x00\x00\x0E\x87\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 0)) e1copy = OpenSSL::BN.new(@e1) assert_equal(@e1, e1copy) e1copy.clear_bit!(0) #=> 998 assert_not_equal(@e1, e1copy) assert_equal(@e1, OpenSSL::BN.new(999)) assert_equal(@e2, OpenSSL::BN.new(-999)) assert_equal(@e3, OpenSSL::BN.new(2**107-1)) assert_equal(@e4, OpenSSL::BN.new(-(2**107-1))) assert_equal(@e1, 999.to_bn) assert_equal(@e2, -999.to_bn) assert_equal(@e3, (2**107-1).to_bn) assert_equal(@e4, (-(2**107-1)).to_bn) end def test_to_str assert_equal("999", @e1.to_s(10)) assert_equal("-999", @e2.to_s(10)) assert_equal((2**107-1).to_s, @e3.to_s(10)) assert_equal((-(2**107-1)).to_s, @e4.to_s(10)) assert_equal("999", @e1.to_s) assert_equal("03E7", @e1.to_s(16)) assert_equal("-03E7", @e2.to_s(16)) assert_equal("07FFFFFFFFFFFFFFFFFFFFFFFFFF", @e3.to_s(16)) assert_equal("-07FFFFFFFFFFFFFFFFFFFFFFFFFF", @e4.to_s(16)) assert_equal("\x03\xe7", @e1.to_s(2)) assert_equal("\x03\xe7", @e2.to_s(2)) assert_equal("\x07\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff", @e3.to_s(2)) assert_equal("\x07\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff", @e4.to_s(2)) assert_equal("\x00\x00\x00\x02\x03\xe7", @e1.to_s(0)) assert_equal("\x00\x00\x00\x02\x83\xe7", @e2.to_s(0)) assert_equal("\x00\x00\x00\x0e\x07\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff", @e3.to_s(0)) assert_equal("\x00\x00\x00\x0e\x87\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff", @e4.to_s(0)) end def test_to_int assert_equal(999, @e1.to_i) assert_equal(-999, @e2.to_i) assert_equal(2**107-1, @e3.to_i) assert_equal(-(2**107-1), @e4.to_i) assert_equal(999, @e1.to_int) end def test_coerce assert_equal(["", "-999"], @e2.coerce("")) assert_equal([1000, -999], @e2.coerce(1000)) assert_raise(TypeError) { @e2.coerce(Class.new.new) } end def test_zero_p assert_equal(true, 0.to_bn.zero?) assert_equal(false, 1.to_bn.zero?) end def test_one_p assert_equal(true, 1.to_bn.one?) assert_equal(false, 2.to_bn.one?) end def test_odd_p assert_equal(true, 1.to_bn.odd?) assert_equal(false, 2.to_bn.odd?) end def test_negative_p assert_equal(false, 0.to_bn.negative?) assert_equal(false, @e1.negative?) assert_equal(true, @e2.negative?) end def test_sqr assert_equal(1, 1.to_bn.sqr) assert_equal(100, 10.to_bn.sqr) end def test_four_ops assert_equal(3, 1.to_bn + 2) assert_equal(-1, 1.to_bn + -2) assert_equal(-1, 1.to_bn - 2) assert_equal(3, 1.to_bn - -2) assert_equal(2, 1.to_bn * 2) assert_equal(-2, 1.to_bn * -2) assert_equal([0, 1], 1.to_bn / 2) assert_equal([2, 0], 2.to_bn / 1) assert_raise(OpenSSL::BNError) { 1.to_bn / 0 } end def test_unary_plus_minus assert_equal(999, +@e1) assert_equal(-999, +@e2) assert_equal(-999, -@e1) assert_equal(+999, -@e2) # These methods create new BN instances due to BN mutability # Ensure that the instance isn't the same e1_plus = +@e1 e1_minus = -@e1 assert_equal(false, @e1.equal?(e1_plus)) assert_equal(true, @e1 == e1_plus) assert_equal(false, @e1.equal?(e1_minus)) end def test_abs assert_equal(@e1, @e2.abs) assert_equal(@e3, @e4.abs) assert_not_equal(@e2, @e2.abs) assert_not_equal(@e4, @e4.abs) assert_equal(false, @e2.abs.negative?) assert_equal(false, @e4.abs.negative?) assert_equal(true, (-@e1.abs).negative?) assert_equal(true, (-@e2.abs).negative?) assert_equal(true, (-@e3.abs).negative?) assert_equal(true, (-@e4.abs).negative?) end def test_mod assert_equal(1, 1.to_bn % 2) assert_equal(0, 2.to_bn % 1) assert_equal(-2, -2.to_bn % 7) end def test_exp assert_equal(1, 1.to_bn ** 5) assert_equal(32, 2.to_bn ** 5) end def test_gcd assert_equal(1, 7.to_bn.gcd(5)) assert_equal(8, 24.to_bn.gcd(16)) end def test_mod_sqr assert_equal(4, 3.to_bn.mod_sqr(5)) assert_equal(0, 59.to_bn.mod_sqr(59)) end def test_mod_sqrt assert_equal(4, 4.to_bn.mod_sqrt(5).mod_sqr(5)) # One of 189484 or 326277 is returned as a square root of 2 (mod 515761). assert_equal(2, 2.to_bn.mod_sqrt(515761).mod_sqr(515761)) assert_equal(0, 5.to_bn.mod_sqrt(5)) assert_raise(OpenSSL::BNError) { 3.to_bn.mod_sqrt(5) } end def test_mod_inverse assert_equal(2, 3.to_bn.mod_inverse(5)) assert_raise(OpenSSL::BNError) { 3.to_bn.mod_inverse(6) } end def test_mod_add assert_equal(1, 3.to_bn.mod_add(5, 7)) assert_equal(2, 3.to_bn.mod_add(5, 3)) assert_equal(5, 3.to_bn.mod_add(-5, 7)) end def test_mod_sub assert_equal(1, 11.to_bn.mod_sub(3, 7)) assert_equal(2, 11.to_bn.mod_sub(3, 3)) assert_equal(5, 3.to_bn.mod_sub(5, 7)) end def test_mod_mul assert_equal(1, 2.to_bn.mod_mul(4, 7)) assert_equal(5, 2.to_bn.mod_mul(-1, 7)) end def test_mod_exp assert_equal(1, 3.to_bn.mod_exp(2, 8)) assert_equal(4, 2.to_bn.mod_exp(5, 7)) end def test_bit_operations e = 0b10010010.to_bn assert_equal(0b10010011, e.set_bit!(0)) assert_equal(0b10010011, e.set_bit!(1)) assert_equal(0b1010010011, e.set_bit!(9)) e = 0b10010010.to_bn assert_equal(0b10010010, e.clear_bit!(0)) assert_equal(0b10010000, e.clear_bit!(1)) e = 0b10010010.to_bn assert_equal(0b10010010, e.mask_bits!(8)) assert_equal(0b10, e.mask_bits!(3)) e = 0b10010010.to_bn assert_equal(false, e.bit_set?(0)) assert_equal(true, e.bit_set?(1)) assert_equal(false, e.bit_set?(1000)) e = 0b10010010.to_bn assert_equal(0b1001001000, e << 2) assert_equal(0b10010010, e) assert_equal(0b1001001000, e.lshift!(2)) assert_equal(0b1001001000, e) e = 0b10010010.to_bn assert_equal(0b100100, e >> 2) assert_equal(0b10010010, e) assert_equal(0b100100, e.rshift!(2)) assert_equal(0b100100, e) end def test_random 10.times { r1 = OpenSSL::BN.rand(8) assert_include(128..255, r1) r2 = OpenSSL::BN.rand(8, -1) assert_include(0..255, r2) r3 = OpenSSL::BN.rand(8, 1) assert_include(192..255, r3) r4 = OpenSSL::BN.rand(8, 1, true) assert_include(192..255, r4) assert_equal(true, r4.odd?) r5 = OpenSSL::BN.rand_range(256) assert_include(0..255, r5) } # Aliases assert_include(128..255, OpenSSL::BN.pseudo_rand(8)) assert_include(0..255, OpenSSL::BN.pseudo_rand_range(256)) end begin require "prime" def test_prime p1 = OpenSSL::BN.generate_prime(32) assert_include(0...2**32, p1) assert_equal(true, Prime.prime?(p1.to_i)) p2 = OpenSSL::BN.generate_prime(32, true) assert_equal(true, Prime.prime?((p2.to_i - 1) / 2)) p3 = OpenSSL::BN.generate_prime(32, false, 4) assert_equal(1, p3 % 4) p4 = OpenSSL::BN.generate_prime(32, false, 4, 3) assert_equal(3, p4 % 4) assert_equal(true, p1.prime?) assert_equal(true, p2.prime?) assert_equal(true, p3.prime?) assert_equal(true, p4.prime?) assert_equal(true, @e3.prime?) assert_equal(true, @e3.prime_fasttest?) end rescue LoadError # prime is the bundled gems at Ruby 3.1 end def test_num_bits_bytes assert_equal(10, @e1.num_bits) assert_equal(2, @e1.num_bytes) assert_equal(107, @e3.num_bits) assert_equal(14, @e3.num_bytes) assert_equal(0, 0.to_bn.num_bits) assert_equal(0, 0.to_bn.num_bytes) assert_equal(9, -256.to_bn.num_bits) assert_equal(2, -256.to_bn.num_bytes) end def test_comparison assert_equal(false, @e1 == nil) assert_equal(false, @e1 == -999) assert_equal(true, @e1 == 999) assert_equal(true, @e1 == 999.to_bn) assert_equal(false, @e1.eql?(nil)) assert_equal(false, @e1.eql?(999)) assert_equal(true, @e1.eql?(999.to_bn)) assert_equal(@e1.hash, 999.to_bn.hash) assert_not_equal(@e1.hash, @e3.hash) assert_equal(0, @e1.cmp(999)) assert_equal(1, @e1.cmp(-999)) assert_equal(0, @e1.ucmp(999)) assert_equal(0, @e1.ucmp(-999)) assert_instance_of(String, @e1.hash.to_s) end def test_argument_error bug15760 = '[ruby-core:92231] [Bug #15760]' assert_raise(ArgumentError, bug15760) { OpenSSL::BN.new(nil, 2) } end def test_get_flags_and_set_flags return if aws_lc? # AWS-LC does not support BN::CONSTTIME. e = OpenSSL::BN.new(999) assert_equal(0, e.get_flags(OpenSSL::BN::CONSTTIME)) e.set_flags(OpenSSL::BN::CONSTTIME) assert_equal(OpenSSL::BN::CONSTTIME, e.get_flags(OpenSSL::BN::CONSTTIME)) b = OpenSSL::BN.new(2) m = OpenSSL::BN.new(99) assert_equal("17", b.mod_exp(e, m).to_s) # mod_exp fails when m is even and any argument has CONSTTIME flag m = OpenSSL::BN.new(98) assert_raise(OpenSSL::BNError) do b.mod_exp(e, m) end # It looks like flags cannot be removed once enabled e.set_flags(0) assert_equal(4, e.get_flags(OpenSSL::BN::CONSTTIME)) end if defined?(Ractor) && respond_to?(:ractor) unless Ractor.method_defined?(:value) # Ruby 3.4 or earlier using Module.new { refine Ractor do alias value take end } end ractor def test_ractor assert_equal(@e1, Ractor.new { OpenSSL::BN.new("999") }.value) assert_equal(@e3, Ractor.new { OpenSSL::BN.new("\a\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 2) }.value) assert_equal("999", Ractor.new(@e1) { |e1| e1.to_s }.value) assert_equal("07FFFFFFFFFFFFFFFFFFFFFFFFFF", Ractor.new(@e3) { |e3| e3.to_s(16) }.value) assert_equal(2**107-1, Ractor.new(@e3) { _1.to_i }.value) assert_equal([1000, -999], Ractor.new(@e2) { _1.coerce(1000) }.value) assert_equal(false, Ractor.new { 1.to_bn.zero? }.value) assert_equal(true, Ractor.new { 1.to_bn.one? }.value) assert_equal(true, Ractor.new(@e2) { _1.negative? }.value) assert_equal("-03E7", Ractor.new(@e2) { _1.to_s(16) }.value) assert_equal(2**107-1, Ractor.new(@e3) { _1.to_i }.value) assert_equal([1000, -999], Ractor.new(@e2) { _1.coerce(1000) }.value) assert_equal(true, Ractor.new { 0.to_bn.zero? }.value) assert_equal(true, Ractor.new { 1.to_bn.one? }.value ) assert_equal(false,Ractor.new { 2.to_bn.odd? }.value) assert_equal(true, Ractor.new(@e2) { _1.negative? }.value) assert_include(128..255, Ractor.new { OpenSSL::BN.rand(8)}.value) assert_include(0...2**32, Ractor.new { OpenSSL::BN.generate_prime(32) }.value) if !aws_lc? # AWS-LC does not support BN::CONSTTIME. assert_equal(0, Ractor.new { OpenSSL::BN.new(999).get_flags(OpenSSL::BN::CONSTTIME) }.value) end # test if shareable when frozen assert Ractor.shareable?(@e1.freeze) end end end end ================================================ FILE: test/openssl/test_buffering.rb ================================================ # frozen_string_literal: true require_relative 'utils' if defined?(OpenSSL) class OpenSSL::TestBuffering < OpenSSL::TestCase class IO include OpenSSL::Buffering attr_accessor :sync def initialize @io = Buffer.new 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.append_as_bytes(str) str.size end end def setup super @io = IO.new end def test_encoding @io.write '😊' @io.flush assert_equal @io.string.encoding, Encoding::BINARY 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 end ================================================ FILE: test/openssl/test_cipher.rb ================================================ # frozen_string_literal: true require_relative 'utils' if defined?(OpenSSL) 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("AES-256-CBC").encrypt cipher.pkcs5_keyivgen(pass, salt, num, "SHA256") s1 = cipher.update(pt) << cipher.final d1 = num.times.inject(pass + salt) {|out, _| OpenSSL::Digest.digest('SHA256', out) } d2 = num.times.inject(d1 + pass + salt) {|out, _| OpenSSL::Digest.digest('SHA256', out) } key = (d1 + d2)[0, 32] iv = (d1 + d2)[32, 16] cipher = new_encryptor("AES-256-CBC", key: key, iv: iv) s2 = cipher.update(pt) << cipher.final assert_equal s1, s2 cipher2 = OpenSSL::Cipher.new("AES-256-CBC").encrypt assert_raise(ArgumentError) { cipher2.pkcs5_keyivgen(pass, salt, -1, "SHA256") } end def test_info cipher = OpenSSL::Cipher.new("AES-256-CBC").encrypt assert_equal "AES-256-CBC", cipher.name assert_equal 32, cipher.key_len assert_equal 16, 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("AES-256-CBC").encrypt assert_raise(ArgumentError) { cipher.key = "\x01" * 31 } assert_nothing_raised { cipher.key = "\x01" * 32 } assert_raise(ArgumentError) { cipher.key = "\x01" * 33 } assert_raise(ArgumentError) { cipher.iv = "\x01" * 15 } assert_nothing_raised { cipher.iv = "\x01" * 16 } assert_raise(ArgumentError) { cipher.iv = "\x01" * 17 } 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_initialize cipher = OpenSSL::Cipher.new("AES-256-CBC") assert_raise(RuntimeError) { cipher.__send__(:initialize, "AES-256-CBC") } assert_raise(RuntimeError) { OpenSSL::Cipher.allocate.final } assert_raise(OpenSSL::Cipher::CipherError) { OpenSSL::Cipher.new("no such algorithm") } 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 def test_update_with_buffer cipher = OpenSSL::Cipher.new("aes-128-ecb").encrypt cipher.random_key expected = cipher.update("data" * 10) << cipher.final assert_equal 48, expected.bytesize # Buffer is supplied cipher.reset buf = String.new assert_same buf, cipher.update("data" * 10, buf) assert_equal 32, buf.bytesize assert_equal expected, buf + cipher.final # Buffer is frozen cipher.reset assert_raise(FrozenError) { cipher.update("data", String.new.freeze) } # Buffer is a shared string [ruby-core:120141] [Bug #20937] cipher.reset buf = "x".b * 1024 shared = buf[-("data".bytesize * 10 + 32)..-1] assert_same shared, cipher.update("data" * 10, shared) assert_equal expected, shared + cipher.final end def test_ciphers ciphers = OpenSSL::Cipher.ciphers assert_kind_of Array, ciphers assert_include ciphers, "aes-128-cbc" assert_include ciphers, "aes128" # alias of aes-128-cbc assert_include ciphers, "aes-128-gcm" end def test_AES pt = File.read(__FILE__) %w(ecb cbc cfb ofb).each{|mode| c1 = OpenSSL::Cipher.new("aes-256-#{mode}") c1.encrypt c1.pkcs5_keyivgen("passwd", "12345678", 10000, "SHA256") ct = c1.update(pt) + c1.final c2 = OpenSSL::Cipher.new("aes-256-#{mode}") c2.decrypt c2.pkcs5_keyivgen("passwd", "12345678", 10000, "SHA256") 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.new("aes-128-ecb").update "." * 17 end end def test_auth_tag_error_inheritance assert_equal OpenSSL::Cipher::CipherError, OpenSSL::Cipher::AuthTagError.superclass end def test_authenticated cipher = OpenSSL::Cipher.new('aes-128-gcm') assert_predicate(cipher, :authenticated?) cipher = OpenSSL::Cipher.new('aes-128-cbc') assert_not_predicate(cipher, :authenticated?) end def test_aes_ccm # RFC 3610 Section 8, Test Case 1 key = ["c0c1c2c3c4c5c6c7c8c9cacbcccdcecf"].pack("H*") iv = ["00000003020100a0a1a2a3a4a5"].pack("H*") aad = ["0001020304050607"].pack("H*") pt = ["08090a0b0c0d0e0f101112131415161718191a1b1c1d1e"].pack("H*") ct = ["588c979a61c663d2f066d0c2c0f989806d5f6b61dac384"].pack("H*") tag = ["17e8d12cfdf926e0"].pack("H*") kwargs = {auth_tag_len: 8, iv_len: 13, key: key, iv: iv} cipher = new_encryptor("aes-128-ccm", **kwargs, ccm_data_len: pt.length, auth_data: aad) assert_equal ct, cipher.update(pt) << cipher.final assert_equal tag, cipher.auth_tag cipher = new_decryptor("aes-128-ccm", **kwargs, ccm_data_len: ct.length, auth_tag: tag, auth_data: aad) assert_equal pt, cipher.update(ct) << cipher.final # truncated tag is accepted cipher = new_encryptor("aes-128-ccm", **kwargs, ccm_data_len: pt.length, 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-ccm", **kwargs, ccm_data_len: ct.length, auth_tag: tag[0, 8], auth_data: aad) assert_equal pt, cipher.update(ct) << cipher.final # wrong tag is rejected - in CCM, authentication happens during update, but # we consider this a general CipherError since update failures can have various causes tag2 = tag.dup tag2.setbyte(-1, (tag2.getbyte(-1) + 1) & 0xff) cipher = new_decryptor("aes-128-ccm", **kwargs, ccm_data_len: ct.length, auth_tag: tag2, auth_data: aad) assert_raise(OpenSSL::Cipher::CipherError) { cipher.update(ct) } # wrong aad is rejected aad2 = aad[0..-2] << aad[-1].succ cipher = new_decryptor("aes-128-ccm", **kwargs, ccm_data_len: ct.length, auth_tag: tag, auth_data: aad2) assert_raise(OpenSSL::Cipher::CipherError) { cipher.update(ct) } # wrong ciphertext is rejected ct2 = ct[0..-2] << ct[-1].succ cipher = new_decryptor("aes-128-ccm", **kwargs, ccm_data_len: ct2.length, auth_tag: tag, auth_data: aad) assert_raise(OpenSSL::Cipher::CipherError) { cipher.update(ct2) } end if has_cipher?("aes-128-ccm") && OpenSSL::Cipher.new("aes-128-ccm").authenticated? && openssl?(1, 1, 1, 0x03, 0xf) # version >= 1.1.1c 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::AuthTagError) { 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::AuthTagError) { 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::AuthTagError) { cipher.final } end 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 def test_aes_ocb_tag_len # AES-128-OCB is not FIPS-approved. omit_on_fips # 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_siv # AES-128-GCM-SIV is not FIPS-approved. omit_on_fips # RFC 8452 Appendix C.1., 8th example key = ["01000000000000000000000000000000"].pack("H*") iv = ["030000000000000000000000"].pack("H*") aad = ["01"].pack("H*") pt = ["0200000000000000"].pack("H*") ct = ["1e6daba35669f4273b0a1a2560969cdf790d99759abd1508"].pack("H*") tag = ["3b0a1a2560969cdf790d99759abd1508"].pack("H*") ct_without_tag = ct.byteslice(0, ct.bytesize - tag.bytesize) cipher = new_encryptor("aes-128-gcm-siv", key: key, iv: iv, auth_data: aad) assert_equal ct_without_tag, cipher.update(pt) << cipher.final assert_equal tag, cipher.auth_tag cipher = new_decryptor("aes-128-gcm-siv", key: key, iv: iv, auth_tag: tag, auth_data: aad) assert_equal pt, cipher.update(ct_without_tag) << cipher.final end if openssl?(3, 2, 0) 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 def test_aes_keywrap_pad # RFC 5649 Section 6; The second example kek = ["5840df6e29b02af1ab493b705bf16ea1ae8338f4dcc176a8"].pack("H*") key = ["466f7250617369"].pack("H*") wrap = ["afbeb0f07dfbf5419200f2ccb50bb24f"].pack("H*") begin cipher = OpenSSL::Cipher.new("id-aes192-wrap-pad").encrypt rescue OpenSSL::Cipher::CipherError omit "id-aes192-wrap-pad is not supported: #$!" end cipher.key = kek ct = cipher.update(key) << cipher.final assert_equal wrap, ct end def test_non_aead_cipher_set_auth_data assert_raise(OpenSSL::Cipher::CipherError) { cipher = OpenSSL::Cipher.new("aes-128-cfb").encrypt cipher.auth_data = "123" } end def test_crypt_after_key key = ["2b7e151628aed2a6abf7158809cf4f3c"].pack("H*") %w'ecb cbc cfb ctr gcm'.each do |c| cipher = OpenSSL::Cipher.new("aes-128-#{c}") cipher.key = key cipher.encrypt assert_raise(OpenSSL::Cipher::CipherError) { cipher.update("") } cipher = OpenSSL::Cipher.new("aes-128-#{c}") cipher.key = key cipher.decrypt assert_raise(OpenSSL::Cipher::CipherError) { cipher.update("") } end end 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 ================================================ FILE: test/openssl/test_config.rb ================================================ # frozen_string_literal: true require_relative 'utils' if defined?(OpenSSL) 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) assert_predicate(c, :frozen?) end def test_s_parse_format # AWS-LC removed support for parsing $foo variables. return if aws_lc? 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] [dollar ] foo=bar bar = $(foo) baz = 123$(default::bar)456${foo}798 qux = ${baz} quxx = $qux.$qux __EOC__ assert_equal(['default', 'default1 default2', 'dollar', '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']) # dollar assert_equal('bar', c['dollar']['foo']) assert_equal('bar', c['dollar']['bar']) assert_equal('123baz456bar798', c['dollar']['baz']) assert_equal('123baz456bar798', c['dollar']['qux']) assert_equal('123baz456bar798.123baz456bar798', c['dollar']['quxx']) assert_raise_with_message(OpenSSL::ConfigError, /error in line 1: variable has no value/) do OpenSSL::Config.parse("foo = $bar") end assert_raise_with_message(OpenSSL::ConfigError, /error in line 1: no close brace/) do OpenSSL::Config.parse("foo = $(bar") end assert_raise_with_message(OpenSSL::ConfigError, /error in line 1: missing equal sign/) do OpenSSL::Config.parse("f o =b ar # no space in key") end assert_raise_with_message(OpenSSL::ConfigError, /error in line 7: missing close square bracket/) do OpenSSL::Config.parse(<<__EOC__) # comment 1 # comments # # comment 2 \t#comment 3 [second ]\t [third # section not terminated __EOC__ end end def test_s_parse_include if !openssl?(1, 1, 1, 2) # OpenSSL < 1.1.1 parses .include directive as a normal assignment pend ".include directive is not supported" end in_tmpdir("ossl-config-include-test") do |dir| Dir.mkdir("child") File.write("child/a.conf", <<~__EOC__) [default] file-a = a.conf [sec-a] a = 123 __EOC__ File.write("child/b.cnf", <<~__EOC__) [default] file-b = b.cnf [sec-b] b = 123 __EOC__ File.write("include-child.conf", <<~__EOC__) key_outside_section = value_a .include child __EOC__ include_file = <<~__EOC__ [default] file-main = unnamed [sec-main] main = 123 .include = include-child.conf __EOC__ # Include a file by relative path c1 = OpenSSL::Config.parse(include_file) assert_equal(["default", "sec-a", "sec-b", "sec-main"], c1.sections.sort) assert_equal(["file-a", "file-b", "file-main"], c1["default"].keys.sort) assert_equal({"a" => "123"}, c1["sec-a"]) assert_equal({"b" => "123"}, c1["sec-b"]) assert_equal({"main" => "123", "key_outside_section" => "value_a"}, c1["sec-main"]) # Relative paths are from the working directory # Inclusion fails, but the error is ignored silently c2 = Dir.chdir("child") { OpenSSL::Config.parse(include_file) } assert_equal(["default", "sec-main"], c2.sections.sort) end 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_s_parse_config ret = OpenSSL::Config.parse_config(@it.to_s) assert_equal(@it.sections.sort, ret.keys.sort) assert_equal(@it["default"], ret["default"]) end def test_initialize c = OpenSSL::Config.new assert_equal("", c.to_s) assert_equal([], c.sections) assert_predicate(c, :frozen?) 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 unless aws_lc? # AWS-LC does not support the fallback # fallback to 'default' ugly... assert_equal('.', @it.get_value('unknown', 'HOME')) end end def test_get_value_ENV # LibreSSL and AWS-LC removed support for NCONF_get_string(conf, "ENV", str) return if libressl? || aws_lc? 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_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_sections assert_equal(['CA_default', 'ca', 'default'], @it.sections.sort) Tempfile.create("openssl.cnf") { |f| f.write File.read(@tmpfile.path) f.puts "[ new_section ]" f.puts "foo = bar" f.puts "[ empty_section ]" f.close c = OpenSSL::Config.new(f.path) assert_equal(['CA_default', 'ca', 'default', 'empty_section', 'new_section'], c.sections.sort) } 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(/#/, @it.inspect) end def test_dup assert_equal(['CA_default', 'ca', 'default'], @it.sections.sort) c1 = @it.dup assert_predicate(c1, :frozen?) assert_equal(@it.sections.sort, c1.sections.sort) c2 = @it.clone assert_predicate(c2, :frozen?) assert_equal(@it.sections.sort, c2.sections.sort) end if respond_to?(:ractor) ractor def test_ractor assert(Ractor.shareable?(@it)) assert(Ractor.shareable?(OpenSSL::Config.parse("[empty]\n"))) assert(Ractor.shareable?(OpenSSL::Config::DEFAULT_CONFIG_FILE)) end end private def in_tmpdir(*args) Dir.mktmpdir(*args) do |dir| dir = File.realpath(dir) Dir.chdir(dir) do yield dir end end end end end ================================================ FILE: test/openssl/test_digest.rb ================================================ # frozen_string_literal: true require_relative 'utils' if defined?(OpenSSL) class OpenSSL::TestDigest < OpenSSL::TestCase def setup super @d1 = OpenSSL::Digest.new("SHA256") @d2 = OpenSSL::Digest::SHA256.new end def test_initialize assert_raise(OpenSSL::Digest::DigestError) { OpenSSL::Digest.new("no such algorithm") } end def test_digest # SHA256 null value calculated by `echo -n "" | sha256sum` null_hex = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" null_bin = [null_hex].pack("H*") data = "DATA" # SHA256 DATA value calculated by `echo -n "DATA" | sha256sum` hex = "c97c29c7a71b392b437ee03fd17f09bb10b75e879466fc0eb757b2c4a78ac938" 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.digest('SHA256', data)) assert_equal(hex, OpenSSL::Digest.hexdigest('SHA256', data)) end def test_eql assert(@d1 == @d2, "==") d = @d1.clone assert(d == @d1, "clone") end def test_info assert_equal("SHA256", @d1.name, "name") assert_equal("SHA256", @d2.name, "name") assert_equal(32, @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 non_fips_names = %w{MD5} names = %w{SHA1 SHA224 SHA256 SHA384 SHA512} names = non_fips_names + names unless OpenSSL.fips_mode names.each do |name| assert_not_nil(OpenSSL::Digest.new(name)) klass = OpenSSL::Digest.const_get(name.tr('-', '_')) assert_not_nil(klass.new) end end def test_digest_by_oid_and_name # SHA256 o1 = OpenSSL::Digest.digest("SHA256", "") o2 = OpenSSL::Digest.digest("sha256", "") assert_equal(o1, o2) o3 = OpenSSL::Digest.digest("2.16.840.1.101.3.4.2.1", "") assert_equal(o1, o3) # An alias for SHA256 recognized by EVP_get_digestbyname(), but not by # EVP_MD_fetch() o4 = OpenSSL::Digest.digest("RSA-SHA256", "") assert_equal(o1, o4) end def encode16(str) str.unpack1("H*") end def test_sha2 sha224_a = "abd37534c7d9a2efb9465de931cd7055ffdb8879563ae98078d6d6d5" sha256_a = "ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb" sha384_a = "54a59b9f22b0b80880d8427e548b7c23abd873486e1f035dce9cd697e85175033caa88e6d57bc35efae0b5afd3145f31" sha512_a = "1f40fc92da241694750979ee6cf582f2d5d7d28e18335de05abc54d0560e0f5302860c652bf08d560252aa5e74210546f369fbbbce8c12cfc7957b2652fe9a75" assert_equal(sha224_a, OpenSSL::Digest.hexdigest('SHA224', "a")) assert_equal(sha256_a, OpenSSL::Digest.hexdigest('SHA256', "a")) assert_equal(sha384_a, OpenSSL::Digest.hexdigest('SHA384', "a")) assert_equal(sha512_a, OpenSSL::Digest.hexdigest('SHA512', "a")) assert_equal(sha224_a, encode16(OpenSSL::Digest.digest('SHA224', "a"))) assert_equal(sha256_a, encode16(OpenSSL::Digest.digest('SHA256', "a"))) assert_equal(sha384_a, encode16(OpenSSL::Digest.digest('SHA384', "a"))) assert_equal(sha512_a, encode16(OpenSSL::Digest.digest('SHA512', "a"))) end def test_sha512_truncate sha512_224_a = "d5cdb9ccc769a5121d4175f2bfdd13d6310e0d3d361ea75d82108327" sha512_256_a = "455e518824bc0601f9fb858ff5c37d417d67c2f8e0df2babe4808858aea830f8" assert_equal(sha512_224_a, OpenSSL::Digest.hexdigest('SHA512-224', "a")) assert_equal(sha512_256_a, OpenSSL::Digest.hexdigest('SHA512-256', "a")) assert_equal(sha512_224_a, encode16(OpenSSL::Digest.digest('SHA512-224', "a"))) assert_equal(sha512_256_a, encode16(OpenSSL::Digest.digest('SHA512-256', "a"))) end def test_sha3 s224 = '6b4e03423667dbb73b6e15454f0eb1abd4597f9a1b078e3f5b5a6bc7' s256 = 'a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a' s384 = '0c63a75b845e4f7d01107d852e4c2485c51a50aaaa94fc61995e71bbee983a2ac3713831264adb47fb6bd1e058d5f004' s512 = 'a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26' assert_equal(s224, OpenSSL::Digest.hexdigest('SHA3-224', "")) assert_equal(s256, OpenSSL::Digest.hexdigest('SHA3-256', "")) assert_equal(s384, OpenSSL::Digest.hexdigest('SHA3-384', "")) assert_equal(s512, OpenSSL::Digest.hexdigest('SHA3-512', "")) end def test_fetched_evp_md # KECCAK-256 is not FIPS-approved. omit_on_fips # Pre-NIST Keccak is an example of a digest algorithm that doesn't have an # NID and requires dynamic allocation of EVP_MD hex = "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" assert_equal(hex, OpenSSL::Digest.hexdigest("KECCAK-256", "")) end if openssl?(3, 2, 0) def test_openssl_digest assert_equal OpenSSL::Digest::MD5, OpenSSL::Digest("MD5") assert_raise NameError do OpenSSL::Digest("no such digest") end end def test_digests digests = OpenSSL::Digest.digests assert_kind_of Array, digests assert_include digests, "md5" assert_include digests, "sha1" assert_include digests, "sha256" assert_include digests, "sha512" end end end ================================================ FILE: test/openssl/test_engine.rb ================================================ # frozen_string_literal: true require_relative 'utils' if defined?(OpenSSL) && defined?(OpenSSL::Engine) 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 = OpenSSL::Engine.by_id("openssl") 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 = OpenSSL::Engine.by_id("openssl") 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 = OpenSSL::Engine.by_id("openssl") digest = engine.digest("SHA1") assert_not_nil(digest) data = "test" assert_equal(OpenSSL::Digest.digest('SHA1', data), digest.digest(data)) end; end def test_openssl_engine_cipher_rc4 begin OpenSSL::Cipher.new("rc4") rescue OpenSSL::Cipher::CipherError pend "RC4 is not supported" end with_openssl(<<-'end;', ignore_stderr: true) engine = OpenSSL::Engine.by_id("openssl") algo = "RC4" data = "a" * 1000 key = OpenSSL::Random.random_bytes(16) cipher = engine.cipher(algo) cipher.encrypt cipher.key = key encrypted = cipher.update(data) + cipher.final cipher = OpenSSL::Cipher.new(algo) cipher.decrypt cipher.key = key decrypted = cipher.update(encrypted) + cipher.final assert_equal(data, decrypted) end; end private # this is required because OpenSSL::Engine methods change global state def with_openssl(code, **opts) assert_separately(["-ropenssl"], <<~"end;", **opts) #{code} end; end end end ================================================ FILE: test/openssl/test_fips.rb ================================================ # frozen_string_literal: true require_relative 'utils' if defined?(OpenSSL) class OpenSSL::TestFIPS < OpenSSL::TestCase def test_fips_mode_get_is_true_on_fips_mode_enabled unless ENV["TEST_RUBY_OPENSSL_FIPS_ENABLED"] omit "Only for FIPS mode environment" end assert_separately(["-ropenssl"], <<~"end;") assert OpenSSL.fips_mode == true, ".fips_mode should return true on FIPS mode enabled" end; end def test_fips_mode_get_is_false_on_fips_mode_disabled if ENV["TEST_RUBY_OPENSSL_FIPS_ENABLED"] omit "Only for non-FIPS mode environment" end assert_separately(["-ropenssl"], <<~"end;") message = ".fips_mode should return false on FIPS mode disabled. " \ "If you run the test on FIPS mode, please set " \ "TEST_RUBY_OPENSSL_FIPS_ENABLED=true" assert OpenSSL.fips_mode == false, message end; end def test_fips_mode_is_reentrant return if aws_lc? # AWS-LC's FIPS mode is decided at compile time. assert_ruby_status(["-ropenssl"], <<~"end;") OpenSSL.fips_mode = false OpenSSL.fips_mode = false end; end def test_fips_mode_get_with_fips_mode_set return if aws_lc? # AWS-LC's FIPS mode is decided at compile time. unless ENV["TEST_RUBY_OPENSSL_FIPS_ENABLED"] omit "Only for FIPS mode environment" end assert_separately(["-ropenssl"], <<~"end;") begin OpenSSL.fips_mode = true assert OpenSSL.fips_mode == true, ".fips_mode should return true when .fips_mode=true" OpenSSL.fips_mode = false assert OpenSSL.fips_mode == false, ".fips_mode should return false when .fips_mode=false" rescue OpenSSL::OpenSSLError pend "Could not set FIPS mode (OpenSSL::OpenSSLError: \#$!); skipping" end end; end end end ================================================ FILE: test/openssl/test_hmac.rb ================================================ # frozen_string_literal: true require_relative 'utils' if defined?(OpenSSL) 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 assert_equal "kpRyejY4uxwT9I74FYv8nQ==", hmac.base64digest # 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 assert_equal "iW+xEoq73xloMhB81J3zP0e0sRaZErpPU2hLIg==", hmac.base64digest 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 def test_eq h1 = OpenSSL::HMAC.new("KEY", "MD5") h2 = OpenSSL::HMAC.new("KEY", OpenSSL::Digest.new("MD5")) h3 = OpenSSL::HMAC.new("FOO", "MD5") assert_equal h1, h2 refute_equal h1, h2.digest refute_equal h1, h3 end def test_singleton_methods # RFC 2202 2. Test Cases for HMAC-MD5 key = ["0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"].pack("H*") digest = OpenSSL::HMAC.digest("MD5", key, "Hi There") assert_equal ["9294727a3638bb1c13f48ef8158bfc9d"].pack("H*"), digest hexdigest = OpenSSL::HMAC.hexdigest("MD5", key, "Hi There") assert_equal "9294727a3638bb1c13f48ef8158bfc9d", hexdigest b64digest = OpenSSL::HMAC.base64digest("MD5", key, "Hi There") assert_equal "kpRyejY4uxwT9I74FYv8nQ==", b64digest end def test_zero_length_key # Empty string as the key hexdigest = OpenSSL::HMAC.hexdigest("SHA256", "\0"*32, "test") assert_equal "43b0cef99265f9e34c10ea9d3501926d27b39f57c6d674561d8ba236e7a819fb", hexdigest hexdigest = OpenSSL::HMAC.hexdigest("SHA256", "", "test") assert_equal "43b0cef99265f9e34c10ea9d3501926d27b39f57c6d674561d8ba236e7a819fb", hexdigest end end end ================================================ FILE: test/openssl/test_kdf.rb ================================================ # frozen_string_literal: true require_relative 'utils' if defined?(OpenSSL) class OpenSSL::TestKDF < OpenSSL::TestCase def test_pkcs5_pbkdf2_hmac_compatibility expected = OpenSSL::KDF.pbkdf2_hmac("password", salt: "salt", iterations: 1, length: 20, hash: "sha1") assert_equal(expected, OpenSSL::PKCS5.pbkdf2_hmac("password", "salt", 1, 20, "sha1")) assert_equal(expected, OpenSSL::PKCS5.pbkdf2_hmac_sha1("password", "salt", 1, 20)) end 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::KDF.pbkdf2_hmac(p, salt: s, iterations: c, length: dk_len, hash: "sha1") 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::KDF.pbkdf2_hmac(p, salt: s, iterations: c, length: dk_len, hash: "sha1") 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::KDF.pbkdf2_hmac(p, salt: s, iterations: c, length: dk_len, hash: "sha1") 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::KDF.pbkdf2_hmac(p, salt: s, iterations: c, length: dk_len, hash: "sha1") # 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::KDF.pbkdf2_hmac(p, salt: s, iterations: c, length: dk_len, hash: "sha1") 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::KDF.pbkdf2_hmac(p, salt: s, iterations: c, length: dk_len, hash: "sha1") 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 value1 = OpenSSL::KDF.pbkdf2_hmac(p, salt: s, iterations: c, length: dk_len, hash: "sha256") value2 = OpenSSL::KDF.pbkdf2_hmac(p, salt: s, iterations: c, length: dk_len, hash: "sha256") assert_equal(value1, value2) end def test_scrypt_rfc7914_first pend "scrypt is not implemented" unless OpenSSL::KDF.respond_to?(:scrypt) # OpenSSL >= 1.1.0 pass = "" salt = "" n = 16 r = 1 p = 1 dklen = 64 expected = B(%w{ 77 d6 57 62 38 65 7b 20 3b 19 ca 42 c1 8a 04 97 f1 6b 48 44 e3 07 4a e8 df df fa 3f ed e2 14 42 fc d0 06 9d ed 09 48 f8 32 6a 75 3a 0f c8 1f 17 e8 d3 e0 fb 2e 0d 36 28 cf 35 e2 0c 38 d1 89 06 }) assert_equal(expected, OpenSSL::KDF.scrypt(pass, salt: salt, N: n, r: r, p: p, length: dklen)) end def test_scrypt_rfc7914_second pend "scrypt is not implemented" unless OpenSSL::KDF.respond_to?(:scrypt) # OpenSSL >= 1.1.0 pass = "password" salt = "NaCl" n = 1024 r = 8 p = 16 dklen = 64 expected = B(%w{ fd ba be 1c 9d 34 72 00 78 56 e7 19 0d 01 e9 fe 7c 6a d7 cb c8 23 78 30 e7 73 76 63 4b 37 31 62 2e af 30 d9 2e 22 a3 88 6f f1 09 27 9d 98 30 da c7 27 af b9 4a 83 ee 6d 83 60 cb df a2 cc 06 40 }) assert_equal(expected, OpenSSL::KDF.scrypt(pass, salt: salt, N: n, r: r, p: p, length: dklen)) end def test_hkdf_rfc5869_test_case_1 hash = "sha256" ikm = B("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b") salt = B("000102030405060708090a0b0c") info = B("f0f1f2f3f4f5f6f7f8f9") l = 42 okm = B("3cb25f25faacd57a90434f64d0362f2a" \ "2d2d0a90cf1a5a4c5db02d56ecc4c5bf" \ "34007208d5b887185865") assert_equal(okm, OpenSSL::KDF.hkdf(ikm, salt: salt, info: info, length: l, hash: hash)) end def test_hkdf_rfc5869_test_case_3 hash = "sha256" ikm = B("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b") salt = B("") info = B("") l = 42 okm = B("8da4e775a563c18f715f802a063c5a31" \ "b8a11f5c5ee1879ec3454e5f3c738d2d" \ "9d201395faa4b61a96c8") assert_equal(okm, OpenSSL::KDF.hkdf(ikm, salt: salt, info: info, length: l, hash: hash)) end def test_hkdf_rfc5869_test_case_4 hash = "sha1" ikm = B("0b0b0b0b0b0b0b0b0b0b0b") salt = B("000102030405060708090a0b0c") info = B("f0f1f2f3f4f5f6f7f8f9") l = 42 okm = B("085a01ea1b10f36933068b56efa5ad81" \ "a4f14b822f5b091568a9cdd4f155fda2" \ "c22e422478d305f3f896") assert_equal(okm, OpenSSL::KDF.hkdf(ikm, salt: salt, info: info, length: l, hash: hash)) end private def B(ary) [Array(ary).join].pack("H*") end end end ================================================ FILE: test/openssl/test_ns_spki.rb ================================================ # frozen_string_literal: true require_relative 'utils' if defined?(OpenSSL) 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 = Fixtures.pkey("rsa-1") key2 = Fixtures.pkey("rsa-2") spki = OpenSSL::Netscape::SPKI.new spki.challenge = "RandomString" spki.public_key = key1.public_key spki.sign(key1, OpenSSL::Digest.new('SHA256')) 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.unpack1("m"), spki.to_der) assert_equal("MozillaIsMyFriend", spki.challenge) assert_equal(OpenSSL::PKey::RSA, spki.public_key.class) spki = OpenSSL::Netscape::SPKI.new(@b64.unpack1("m")) assert_equal(@b64, spki.to_pem) assert_equal(@b64.unpack1("m"), spki.to_der) assert_equal("MozillaIsMyFriend", spki.challenge) assert_equal(OpenSSL::PKey::RSA, spki.public_key.class) end end end ================================================ FILE: test/openssl/test_ocsp.rb ================================================ # frozen_string_literal: true require_relative "utils" if defined?(OpenSSL) 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 = Fixtures.pkey("rsa-1") 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 = Fixtures.pkey("rsa-2") 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 = Fixtures.pkey("rsa-3") 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 = Fixtures.pkey("p256") 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.new('SHA256')) 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.hexdigest('SHA1', @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) # content of subjectPublicKey (bit string) in SubjectPublicKeyInfo spki = OpenSSL::ASN1.decode(@ca_key.public_to_der) assert_equal OpenSSL::Digest.hexdigest("SHA1", spki.value[1].value), cid.issuer_key_hash end def test_certificate_id_hash_algorithm cid_sha1 = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert, OpenSSL::Digest.new('SHA1')) cid_sha256 = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert, OpenSSL::Digest.new('SHA256')) 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 assert_equal der, OpenSSL::OCSP::CertificateId.new(asn1).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.new('SHA1')) 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("sha256WithRSAEncryption").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) assert_equal true, ret # not signed req = OpenSSL::OCSP::Request.new.add_certid(cid) assert_equal false, req.verify([], store) end def test_request_is_signed cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert) req = OpenSSL::OCSP::Request.new req.add_certid(cid) assert_equal false, req.signed? assert_equal false, OpenSSL::OCSP::Request.new(req.to_der).signed? req.sign(@cert, @cert_key, []) assert_equal true, req.signed? assert_equal true, OpenSSL::OCSP::Request.new(req.to_der).signed? 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.new('SHA1')) 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.new('SHA1')) 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.new('SHA1')) 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_status_good bres = OpenSSL::OCSP::BasicResponse.new cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert, OpenSSL::Digest.new('SHA1')) bres.add_status(cid, OpenSSL::OCSP::V_CERTSTATUS_GOOD, 0, nil, -300, 500, nil) bres.sign(@ocsp_cert, @ocsp_key, [@ca_cert]) statuses = bres.status assert_equal 1, statuses.size status = statuses[0] assert_equal cid.to_der, status[0].to_der assert_equal OpenSSL::OCSP::V_CERTSTATUS_GOOD, status[1] assert_nil status[3] # revtime should be nil for GOOD status end def test_basic_response_status_revoked bres = OpenSSL::OCSP::BasicResponse.new now = Time.at(Time.now.to_i) cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert, OpenSSL::Digest.new('SHA1')) bres.add_status(cid, OpenSSL::OCSP::V_CERTSTATUS_REVOKED, OpenSSL::OCSP::REVOKED_STATUS_UNSPECIFIED, now - 400, -300, nil, nil) bres.sign(@ocsp_cert, @ocsp_key, [@ca_cert]) statuses = bres.status assert_equal 1, statuses.size status = statuses[0] assert_equal OpenSSL::OCSP::V_CERTSTATUS_REVOKED, status[1] assert_equal now - 400, status[3] # revtime should be the revocation time 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.new('SHA1')) cid2 = OpenSSL::OCSP::CertificateId.new(@ocsp_cert, @ca_cert, OpenSSL::Digest.new('SHA1')) cid3 = OpenSSL::OCSP::CertificateId.new(@ca_cert, @ca_cert, OpenSSL::Digest.new('SHA1')) 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 - 300), 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.new('SHA1')) cid2 = OpenSSL::OCSP::CertificateId.new(@ocsp_cert, @ca_cert, OpenSSL::Digest.new('SHA1')) 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) 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) single3 = bres.responses[2] assert_equal false, single3.check_validity end def test_response bres = OpenSSL::OCSP::BasicResponse.new cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert, OpenSSL::Digest.new('SHA1')) 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.new('SHA1')) 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 ================================================ FILE: test/openssl/test_ossl.rb ================================================ # frozen_string_literal: true require_relative "utils" if defined?(OpenSSL) class OpenSSL::TestOSSL < OpenSSL::TestCase def test_fixed_length_secure_compare assert_raise(ArgumentError) { OpenSSL.fixed_length_secure_compare("aaa", "a") } assert_raise(ArgumentError) { OpenSSL.fixed_length_secure_compare("aaa", "aa") } assert_true(OpenSSL.fixed_length_secure_compare("aaa", "aaa")) assert_true(OpenSSL.fixed_length_secure_compare( OpenSSL::Digest.digest('SHA256', "aaa"), OpenSSL::Digest::SHA256.digest("aaa") )) assert_raise(ArgumentError) { OpenSSL.fixed_length_secure_compare("aaa", "aaaa") } assert_false(OpenSSL.fixed_length_secure_compare("aaa", "baa")) assert_false(OpenSSL.fixed_length_secure_compare("aaa", "aba")) assert_false(OpenSSL.fixed_length_secure_compare("aaa", "aab")) assert_raise(ArgumentError) { OpenSSL.fixed_length_secure_compare("aaa", "aaab") } assert_raise(ArgumentError) { OpenSSL.fixed_length_secure_compare("aaa", "b") } assert_raise(ArgumentError) { OpenSSL.fixed_length_secure_compare("aaa", "bb") } assert_false(OpenSSL.fixed_length_secure_compare("aaa", "bbb")) assert_raise(ArgumentError) { OpenSSL.fixed_length_secure_compare("aaa", "bbbb") } end def test_fixed_length_secure_compare_uaf str1 = "A" * 1000000 evil_obj = Object.new evil_obj.define_singleton_method(:to_str) do str1.replace("C" * 1000000) "B" * 1000000 end assert_false(OpenSSL.fixed_length_secure_compare(str1, evil_obj)) end def test_secure_compare assert_false(OpenSSL.secure_compare("aaa", "a")) assert_false(OpenSSL.secure_compare("aaa", "aa")) assert_true(OpenSSL.secure_compare("aaa", "aaa")) assert_false(OpenSSL.secure_compare("aaa", "aaaa")) assert_false(OpenSSL.secure_compare("aaa", "baa")) assert_false(OpenSSL.secure_compare("aaa", "aba")) assert_false(OpenSSL.secure_compare("aaa", "aab")) assert_false(OpenSSL.secure_compare("aaa", "aaab")) assert_false(OpenSSL.secure_compare("aaa", "b")) assert_false(OpenSSL.secure_compare("aaa", "bb")) assert_false(OpenSSL.secure_compare("aaa", "bbb")) assert_false(OpenSSL.secure_compare("aaa", "bbbb")) end def test_memcmp_timing # Ensure using fixed_length_secure_compare takes almost exactly the same amount of time to compare two different strings. # Regular string comparison will short-circuit on the first non-matching character, failing this test. # NOTE: this test may be susceptible to noise if the system running the tests is otherwise under load. a = "x" * 512_000 b = "#{a}y" c = "y#{a}" a = "#{a}x" a_b_time = a_c_time = 0 100.times do t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC) 100.times { OpenSSL.fixed_length_secure_compare(a, b) } t2 = Process.clock_gettime(Process::CLOCK_MONOTONIC) 100.times { OpenSSL.fixed_length_secure_compare(a, c) } t3 = Process.clock_gettime(Process::CLOCK_MONOTONIC) a_b_time += t2 - t1 a_c_time += t3 - t2 end assert_operator(a_b_time, :<, a_c_time * 10, "fixed_length_secure_compare timing test failed") assert_operator(a_c_time, :<, a_b_time * 10, "fixed_length_secure_compare timing test failed") end if ENV["OSSL_TEST_ALL"] == "1" def test_error_data # X509V3_EXT_nconf_nid() called from # OpenSSL::X509::ExtensionFactory#create_ext is a function that uses # ERR_raise_data() to append additional information about the error. # # The generated message should look like: # "subjectAltName = IP:not.a.valid.ip.address: bad ip address (value=not.a.valid.ip.address)" # "subjectAltName = IP:not.a.valid.ip.address: error in extension (name=subjectAltName, value=IP:not.a.valid.ip.address)" # # The string inside parentheses is the ERR_TXT_STRING data, and is appended # by ossl_make_error(), so we check it here. ef = OpenSSL::X509::ExtensionFactory.new e = assert_raise(OpenSSL::X509::ExtensionError) { ef.create_ext("subjectAltName", "IP:not.a.valid.ip.address") } assert_match(/not.a.valid.ip.address\)\z/, e.message) # We currently craft the strings based on ERR_error_string()'s style: # error:::: (data) assert_instance_of(Array, e.errors) assert_match(/\Aerror:.*not.a.valid.ip.address\)\z/, e.errors.last) assert_include(e.detailed_message, "not.a.valid.ip.address") end end end ================================================ FILE: test/openssl/test_pair.rb ================================================ # frozen_string_literal: true require_relative 'utils' require_relative 'ut_eof' if defined?(OpenSSL::SSL) module OpenSSL::SSLPairM def setup svr_dn = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=localhost") ee_exts = [ ["keyUsage", "keyEncipherment,digitalSignature", true], ] @svr_key = OpenSSL::TestUtils::Fixtures.pkey("rsa-1") @svr_cert = issue_cert(svr_dn, @svr_key, 1, ee_exts, nil, nil) end def ssl_pair host = "127.0.0.1" tcps = create_tcp_server(host, 0) port = tcps.connect_address.ip_port th = Thread.new { sctx = OpenSSL::SSL::SSLContext.new sctx.cert = @svr_cert sctx.key = @svr_key sctx.options |= OpenSSL::SSL::OP_NO_COMPRESSION ssls = OpenSSL::SSL::SSLServer.new(tcps, sctx) ns = ssls.accept ssls.close ns } tcpc = create_tcp_client(host, port) c = OpenSSL::SSL::SSLSocket.new(tcpc) c.connect s = th.value yield c, s ensure tcpc&.close tcps&.close s&.close 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) ssl_pair { |s1, s2| begin th = Thread.new { s2 << content; s2.close } yield s1 ensure th&.join end } end end module OpenSSL::TestEOF2M def open_file(content) ssl_pair { |s1, s2| begin th = Thread.new { s1 << content; s1.close } yield s2 ensure th&.join end } end end module OpenSSL::TestPairM def test_getc ssl_pair {|s1, s2| s1 << "a" assert_equal(?a, s2.getc) } end def test_getbyte ssl_pair {|s1, s2| s1 << "a" assert_equal(97, s2.getbyte) } end def test_readbyte ssl_pair {|s1, s2| s1 << "b" assert_equal(98, s2.readbyte) } end def test_readbyte_eof ssl_pair {|s1, s2| s2.close assert_raise(EOFError) { s1.readbyte } } 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_chomp ssl_pair {|s1, s2| s1 << "line1\r\nline2\r\nline3\r\n" s1.close assert_equal("line1", s2.gets("\r\n", chomp: true)) assert_equal("line2\r\n", s2.gets("\r\n", chomp: false)) assert_equal("line3", s2.gets(chomp: true)) } 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 = String.new result << s1.readpartial(10) until result.length == 3 assert_equal("bcd", result) s2.write "efg" result = String.new 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_empty ssl_pair {|s1, s2| s1.puts s1.close assert_equal("\n", s2.read) } end def test_multibyte_read_write # German a umlaut auml = [%w{ C3 A4 }.join('')].pack('H*') auml.force_encoding(Encoding::UTF_8) bsize = auml.bytesize ssl_pair { |s1, s2| assert_equal bsize, s1.write(auml) read = s2.read(bsize) assert_equal Encoding::ASCII_8BIT, read.encoding assert_equal bsize, read.bytesize assert_equal auml, read.force_encoding(Encoding::UTF_8) s1.puts(auml) read = s2.gets assert_equal Encoding::ASCII_8BIT, read.encoding assert_equal bsize + 1, read.bytesize assert_equal auml + "\n", read.force_encoding(Encoding::UTF_8) } 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 IO.select([s2]) 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 IO.select([s2]) assert_equal(nil, s2.read_nonblock(10, exception: false)) } end def test_read_with_outbuf ssl_pair { |s1, s2| s1.write("abc\n") buf = String.new ret = s2.read(2, buf) assert_same ret, buf assert_equal "ab", ret buf = +"garbage" ret = s2.read(2, buf) assert_same ret, buf assert_equal "c\n", ret buf = +"garbage" assert_equal :wait_readable, s2.read_nonblock(100, buf, exception: false) assert_equal "garbage", buf s1.close buf = +"garbage" assert_nil s2.read(100, buf) assert_equal "", buf buf = +"garbage" ret = s2.read(0, buf) assert_same buf, ret assert_equal "", ret } end def test_write_nonblock ssl_pair {|s1, s2| assert_equal 3, s1.write_nonblock("foo") assert_equal "foo", s2.read(3) data = "x" * 16384 written = 0 while true begin written += s1.write_nonblock(data) rescue IO::WaitWritable, IO::WaitReadable break end end assert written > 0 assert_equal written, s2.read(written).bytesize } end def test_write_nonblock_no_exceptions ssl_pair {|s1, s2| assert_equal 3, s1.write_nonblock("foo", exception: false) assert_equal "foo", s2.read(3) data = "x" * 16384 written = 0 while true case ret = s1.write_nonblock(data, exception: false) when :wait_readable, :wait_writable break else written += ret end end assert written > 0 assert_equal written, s2.read(written).bytesize } 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_write_multiple_arguments ssl_pair {|s1, s2| str1 = "foo"; str2 = "bar" assert_equal 6, s1.write(str1, str2) s1.close assert_equal "foobar", s2.read } 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.cert = @svr_cert ctx2.key = @svr_key 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 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.cert = @svr_cert ctx.key = @svr_key sock1, sock2 = tcp_pair th = Thread.new { s2 = OpenSSL::SSL::SSLSocket.new(sock2, ctx) 5.times { begin break s2.accept_nonblock rescue IO::WaitReadable IO.select([s2], nil, nil, 1) rescue IO::WaitWritable IO.select(nil, [s2], nil, 1) end sleep 0.2 } } s1 = OpenSSL::SSL::SSLSocket.new(sock1) 5.times { begin break s1.connect_nonblock rescue IO::WaitReadable IO.select([s1], nil, nil, 1) rescue IO::WaitWritable IO.select(nil, [s1], nil, 1) end sleep 0.2 } s2 = th.value s1.print "a\ndef" assert_equal("a\n", s2.gets) ensure sock1&.close sock2&.close th&.join 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 ================================================ FILE: test/openssl/test_pkcs12.rb ================================================ # frozen_string_literal: true require_relative "utils" if defined?(OpenSSL) # OpenSSL::PKCS12.create calling the PKCS12_create() has the argument mac_iter # which uses a MAC key using PKCS12KDF which is not FIPS-approved. # OpenSSL::PKCS12.new with base64-encoded example calling PKCS12_parse() # verifies the MAC key using PKCS12KDF which is not FIPS-approved. # # PBE-SHA1-3DES uses PKCS12KDF which is not FIPS-approved according to the RFC # 7292 PKCS#12. # https://datatracker.ietf.org/doc/html/rfc7292#appendix-C # > The PBES1 encryption scheme defined in PKCS #5 provides a number of # > algorithm identifiers for deriving keys and IVs; here, we specify a # > few more, all of which use the procedure detailed in Appendices B.2 # > and B.3 to construct keys (and IVs, where needed). As is implied by # > their names, all of the object identifiers below use the hash # > function SHA-1. # > ... # > pbeWithSHAAnd3-KeyTripleDES-CBC OBJECT IDENTIFIER ::= {pkcs-12PbeIds 3} # # Note that the pbeWithSHAAnd3-KeyTripleDES-CBC (pkcs12-pbeids 3) in the RFC # 7292 PKCS#12 means PBE-SHA1-3DES in OpenSSL. PKCS12KDF is used in PKCS#12. # https://oidref.com/1.2.840.113549.1.12.1.3 # https://github.com/openssl/openssl/blob/ed57d1e06dca28689190e00d9893e0fd7ecc67c1/crypto/objects/objects.txt#L385 return if OpenSSL.fips_mode module OpenSSL class TestPKCS12 < OpenSSL::TestCase DEFAULT_PBE_PKEYS = "PBE-SHA1-3DES" DEFAULT_PBE_CERTS = "PBE-SHA1-3DES" 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], ] ca_key = Fixtures.pkey("rsa-1") @cacert = issue_cert(ca, ca_key, 1, ca_exts, nil, nil) inter_ca = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=Intermediate CA") inter_ca_key = Fixtures.pkey("rsa-2") @inter_cacert = issue_cert(inter_ca, inter_ca_key, 2, ca_exts, @cacert, ca_key) exts = [ ["keyUsage","digitalSignature",true], ["subjectKeyIdentifier","hash",false], ] ee = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=Ruby PKCS12 Test Certificate") @mykey = Fixtures.pkey("rsa-3") @mycert = issue_cert(ee, @mykey, 3, exts, @inter_cacert, inter_ca_key) end def test_create_single_key_single_cert pkcs12 = OpenSSL::PKCS12.create( "omg", "hello", @mykey, @mycert, nil, DEFAULT_PBE_PKEYS, DEFAULT_PBE_CERTS, ) assert_equal @mycert, pkcs12.certificate assert_equal @mykey.to_der, pkcs12.key.to_der assert_nil pkcs12.ca_certs der = pkcs12.to_der decoded = OpenSSL::PKCS12.new(der, "omg") assert_equal @mykey.to_der, decoded.key.to_der assert_equal @mycert, decoded.certificate assert_equal [], Array(decoded.ca_certs) end def test_create_no_pass pkcs12 = OpenSSL::PKCS12.create( nil, "hello", @mykey, @mycert, nil, DEFAULT_PBE_PKEYS, DEFAULT_PBE_CERTS, ) assert_equal @mycert, pkcs12.certificate assert_equal @mykey.to_der, pkcs12.key.to_der assert_nil pkcs12.ca_certs decoded = OpenSSL::PKCS12.new(pkcs12.to_der) assert_equal @mycert, decoded.certificate end def test_create_with_chain chain = [@inter_cacert, @cacert] pkcs12 = OpenSSL::PKCS12.create( "omg", "hello", @mykey, @mycert, chain, DEFAULT_PBE_PKEYS, DEFAULT_PBE_CERTS, ) 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", @mykey, @mycert, chain, DEFAULT_PBE_PKEYS, DEFAULT_PBE_CERTS, ) decoded = OpenSSL::PKCS12.new(pkcs12.to_der, passwd) assert_equal chain.size, decoded.ca_certs.size assert_include decoded.ca_certs, @cacert assert_include decoded.ca_certs, @inter_cacert assert_equal @mycert, decoded.certificate assert_equal @mykey.to_der, decoded.key.to_der end def test_create_with_bad_nid assert_raise(ArgumentError) do OpenSSL::PKCS12.create( "omg", "hello", @mykey, @mycert, [], "foo" ) end end def test_create_with_itr OpenSSL::PKCS12.create( "omg", "hello", @mykey, @mycert, [], DEFAULT_PBE_PKEYS, DEFAULT_PBE_CERTS, 2048 ) assert_raise(TypeError) do OpenSSL::PKCS12.create( "omg", "hello", @mykey, @mycert, [], DEFAULT_PBE_PKEYS, DEFAULT_PBE_CERTS, "omg" ) end end def test_create_with_mac_itr OpenSSL::PKCS12.create( "omg", "hello", @mykey, @mycert, [], DEFAULT_PBE_PKEYS, DEFAULT_PBE_CERTS, nil, 2048 ) assert_raise(TypeError) do OpenSSL::PKCS12.create( "omg", "hello", @mykey, @mycert, [], DEFAULT_PBE_PKEYS, DEFAULT_PBE_CERTS, nil, "omg" ) end end def test_create_with_keytype omit "AWS-LC does not support KEY_SIG and KEY_EX" if aws_lc? OpenSSL::PKCS12.create( "omg", "hello", @mykey, @mycert, [], DEFAULT_PBE_PKEYS, DEFAULT_PBE_CERTS, nil, nil, OpenSSL::PKCS12::KEY_SIG ) assert_raise(ArgumentError) do OpenSSL::PKCS12.create( "omg", "hello", @mykey, @mycert, [], DEFAULT_PBE_PKEYS, DEFAULT_PBE_CERTS, nil, nil, 2048 ) end end def test_new_with_no_keys # Generated with the following steps: # Print the value of the @mycert such as by `puts @mycert.to_s` and # save the value as the file `mycert.pem`. # Run the following commands: # openssl pkcs12 -certpbe PBE-SHA1-3DES -in <(cat mycert.pem) \ # -nokeys -export -passout pass:abc123 -out /tmp/p12.out # base64 -w 60 /tmp/p12.out str = <<~EOF.unpack1("m") MIIGJAIBAzCCBeoGCSqGSIb3DQEHAaCCBdsEggXXMIIF0zCCBc8GCSqGSIb3 DQEHBqCCBcAwggW8AgEAMIIFtQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQMw DgQIjv5c3OHvnBgCAggAgIIFiMJa8Z/w7errRvCQPXh9dGQz3eJaFq3S2gXD rh6oiwsgIRJZvYAWgU6ll9NV7N5SgvS2DDNVuc3tsP8TPWjp+bIxzS9qmGUV kYWuURWLMKhpF12ZRDab8jcIwBgKoSGiDJk8xHjx6L613/XcRM6ln3VeQK+C hlW5kXniNAUAgTft25Fn61Xa8xnhmsz/fk1ycGnyGjKCnr7Mgy7KV0C1vs23 18n8+b1ktDWLZPYgpmXuMFVh0o+HJTV3O86mkIhJonMcnOMgKZ+i8KeXaocN JQlAPBG4+HOip7FbQT/h6reXv8/J+hgjLfqAb5aV3m03rUX9mXx66nR1tQU0 Jq+XPfDh5+V4akIczLlMyyo/xZjI1/qupcMjr+giOGnGd8BA3cuXW+ueLQiA PpTp+DQLVHRfz9XTZbyqOReNEtEXvO9gOlKSEY5lp65ItXVEs2Oqyf9PfU9y DUltN6fCMilwPyyrsIBKXCu2ZLM5h65KVCXAYEX9lNqj9zrQ7vTqvCNN8RhS ScYouTX2Eqa4Z+gTZWLHa8RCQFoyP6hd+97/Tg2Gv2UTH0myQxIVcnpdi1wy cqb+er7tyKbcO96uSlUjpj/JvjlodtjJcX+oinEqGb/caj4UepbBwiG3vv70 63bS3jTsOLNjDRsR9if3LxIhLa6DW8zOJiGC+EvMD1o4dzHcGVpQ/pZWCHZC +YiNJpQOBApiZluE+UZ0m3XrtHFQYk7xblTrh+FJF91wBsok0rZXLAKd8m4p OJsc7quCq3cuHRRTzJQ4nSe01uqbwGDAYwLvi6VWy3svU5qa05eDRmgzEFTG e84Gp/1LQCtpQFr4txkjFchO2whWS80KoQKqmLPyGm1D9Lv53Q4ZsKMgNihs rEepuaOZMKHl4yMAYFoOXZCAYzfbhN6b2phcFAHjMUHUw9e3F0QuDk9D0tsr riYTrkocqlOKfK4QTomx27O0ON2J6f1rtEojGgfl9RNykN7iKGzjS3914QjW W6gGiZejxHsDPEAa4gUp0WiSUSXtD5WJgoyAzLydR2dKWsQ4WlaUXi01CuGy +xvncSn2nO3bbot8VD5H6XU1CjREVtnIfbeRYO/uofyLUP3olK5RqN6ne6Xo eXnJ/bjYphA8NGuuuvuW1SCITmINkZDLC9cGlER9+K65RR/DR3TigkexXMeN aJ70ivZYAl0OuhZt3TGIlAzS64TIoyORe3z7Ta1Pp9PZQarYJpF9BBIZIFor 757PHHuQKRuugiRkp8B7v4eq1BQ+VeAxCKpyZ7XrgEtbY/AWDiaKcGPKPjc3 AqQraVeQm7kMBT163wFmZArCphzkDOI3bz2oEO8YArMgLq2Vto9jAZlqKyWr pi2bSJxuoP1aoD58CHcWMrf8/j1LVdQhKgHQXSik2ID0H2Wc/XnglhzlVFuJ JsNIW/EGJlZh/5WDez9U0bXqnBlu3uasPEOezdoKlcCmQlmTO5+uLHYLEtNA EH9MtnGZebi9XS5meTuS6z5LILt8O9IHZxmT3JRPHYj287FEzotlLdcJ4Ee5 enW41UHjLrfv4OaITO1hVuoLRGdzjESx/fHMWmxroZ1nVClxECOdT42zvIYJ J3xBZ0gppzQ5fjoYiKjJpxTflRxUuxshk3ih6VUoKtqj/W18tBQ3g5SOlkgT yCW8r74yZlfYmNrPyDMUQYpLUPWj2n71GF0KyPfTU5yOatRgvheh262w5BG3 omFY7mb3tCv8/U2jdMIoukRKacpZiagofz3SxojOJq52cHnCri+gTHBMX0cO j58ygfntHWRzst0pV7Ze2X3fdCAJ4DokH6bNJNthcgmolFJ/y3V1tJjgsdtQ 7Pjn/vE6xUV0HXE2x4yoVYNirbAMIvkN/X+atxrN0dA4AchN+zGp8TAxMCEw CQYFKw4DAhoFAAQUQ+6XXkyhf6uYgtbibILN2IjKnOAECLiqoY45MPCrAgII AA== 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, p12.ca_certs[0].subject end def test_new_with_no_certs # Generated with the folowing steps: # openssl pkcs12 -inkey test/openssl/fixtures/pkey/rsa-1.pem \ # -nocerts -export -passout pass:abc123 -out /tmp/p12.out # base64 -w 60 /tmp/p12.out str = <<~EOF.unpack1("m") MIIJ7wIBAzCCCbUGCSqGSIb3DQEHAaCCCaYEggmiMIIJnjCCCZoGCSqGSIb3 DQEHAaCCCYsEggmHMIIJgzCCCX8GCyqGSIb3DQEMCgECoIIJbjCCCWowHAYK KoZIhvcNAQwBAzAOBAjX5nN8jyRKwQICCAAEgglIBIRLHfiY1mNHpl3FdX6+ 72L+ZOVXnlZ1MY9HSeg0RMkCJcm0mJ2UD7INUOGXvwpK9fr6WJUZM1IqTihQ 1dM0crRC2m23aP7KtAlXh2DYD3otseDtwoN/NE19RsiJzeIiy5TSW1d47weU +D4Ig/9FYVFPTDgMzdCxXujhvO/MTbZIjqtcS+IOyF+91KkXrHkfkGjZC7KS WRmYw9BBuIPQEewdTI35sAJcxT8rK7JIiL/9mewbSE+Z28Wq1WXwmjL3oZm9 lw6+f515b197GYEGomr6LQqJJamSYpwQbTGHonku6Tf3ylB4NLFqOnRCKE4K zRSSYIqJBlKHmQ4pDm5awoupHYxMZLZKZvXNYyYN3kV8r1iiNVlY7KBR4CsX rqUkXehRmcPnuqEMW8aOpuYe/HWf8PYI93oiDZjcEZMwW2IZFFrgBbqUeNCM CQTkjAYxi5FyoaoTnHrj/aRtdLOg1xIJe4KKcmOXAVMmVM9QEPNfUwiXJrE7 n42gl4NyzcZpxqwWBT++9TnQGZ/lEpwR6dzkZwICNQLdQ+elsdT7mumywP+1 WaFqg9kpurimaiBu515vJNp9Iqv1Nmke6R8Lk6WVRKPg4Akw0fkuy6HS+LyN ofdCfVUkPGN6zkjAxGZP9ZBwvXUbLRC5W3N5qZuAy5WcsS75z+oVeX9ePV63 cue23sClu8JSJcw3HFgPaAE4sfkQ4MoihPY5kezgT7F7Lw/j86S0ebrDNp4N Y685ec81NRHJ80CAM55f3kGCOEhoifD4VZrvr1TdHZY9Gm3b1RYaJCit2huF nlOfzeimdcv/tkjb6UsbpXx3JKkF2NFFip0yEBERRCdWRYMUpBRcl3ad6XHy w0pVTgIjTxGlbbtOCi3siqMOK0GNt6UgjoEFc1xqjsgLwU0Ta2quRu7RFPGM GoEwoC6VH23p9Hr4uTFOL0uHfkKWKunNN+7YPi6LT6IKmTQwrp+fTO61N6Xh KlqTpwESKsIJB2iMnc8wBkjXJtmG/e2n5oTqfhICIrxYmEb7zKDyK3eqeTj3 FhQh2t7cUIiqcT52AckUqniPmlE6hf82yBjhaQUPfi/ExTBtTDSmFfRPUzq+ Rlla4OHllPRzUXJExyansgCxZbPqlw46AtygSWRGcWoYAKUKwwoYjerqIV5g JoZICV9BOU9TXco1dHXZQTs/nnTwoRmYiL/Ly5XpvUAnQOhYeCPjBeFnPSBR R/hRNqrDH2MOV57v5KQIH2+mvy26tRG+tVGHmLMaOJeQkjLdxx+az8RfXIrH 7hpAsoBb+g9jUDY1mUVavPk1T45GMpQH8u3kkzRvChfOst6533GyIZhE7FhN KanC6ACabVFDUs6P9pK9RPQMp1qJfpA0XJFx5TCbVbPkvnkZd8K5Tl/tzNM1 n32eRao4MKr9KDwoDL93S1yJgYTlYjy1XW/ewdedtX+B4koAoz/wSXDYO+GQ Zu6ZSpKSEHTRPhchsJ4oICvpriVaJkn0/Z7H3YjNMB9U5RR9+GiIg1wY1Oa1 S3WfuwrrI6eqfbQwj6PDNu3IKy6srEgvJwaofQALNBPSYWbauM2brc8qsD+t n8jC/aD1aMcy00+9t3H/RVCjEOb3yKfUpAldIkEA2NTTnZpoDQDXeNYU2F/W yhmFjJy8A0O4QOk2xnZK9kcxSRs0v8vI8HivvgWENoVPscsDC4742SSIe6SL f/T08reIX11f0K70rMtLhtFMQdHdYOTNl6JzhkHPLr/f9MEZsBEQx52depnF ARb3gXGbCt7BAi0OeCEBSbLr2yWuW4r55N0wRZSOBtgqgjsiHP7CDQSkbL6p FPlQS1do9gBSHiNYvsmN1LN5bG+mhcVb0UjZub4mL0EqGadjDfDdRJmWqlX0 r5dyMcOWQVy4O2cPqYFlcP9lk8buc5otcyVI2isrAFdlvBK29oK6jc52Aq5Q 0b2ESDlgX8WRgiOPPxK8dySKEeuIwngCtJyNTecP9Ug06TDsu0znZGCXJ+3P 8JOpykgA8EQdOZOYHbo76ZfB2SkklI5KeRA5IBjGs9G3TZ4PHLy2DIwsbWzS H1g01o1x264nx1cJ+eEgUN/KIiGFIib42RS8Af4D5e+Vj54Rt3axq+ag3kI+ 53p8uotyu+SpvvXUP7Kv4xpQ/L6k41VM0rfrd9+DrlDVvSfxP2uh6I1TKF7A CT5n8zguMbng4PGjxvyPBM5k62t6hN5fuw6Af0aZFexh+IjB/5wFQ6onSz23 fBzMW4St7RgSs8fDg3lrM+5rwXiey1jxY1ddaxOoUsWRMvvdd7rZxRZQoN5v AcI5iMkK/vvpQgC/sfzhtXtrJ2XOPZ+GVgi7VcuDLKSkdFMcPbGzO8SdxUnS SLV5XTKqKND+Lrfx7DAoKi5wbDFHu5496/MHK5qP4tBe6sJ5bZc+KDJIH46e wTV1oWtB5tV4q46hOb5WRcn/Wjz3HSKaGZgx5QbK1MfKTzD5CTUn+ArMockX 2wJhPnFK85U4rgv8iBuh9bRjyw+YaKf7Z3loXRiE1eRG6RzuPF0ZecFiDumk AC/VUXynJhzePBLqzrQj0exanACdullN+pSfHiRWBxR2VFUkjoFP5X45GK3z OstSH6FOkMVU4afqEmjsIwozDFIyin5EyWTtdhJe3szdJSGY23Tut+9hUatx 9FDFLESOd8z3tyQSNiLk/Hib+e/lbjxqbXBG/p/oyvP3N999PLUPtpKqtYkV H0+18sNh9CVfojiJl44fzxe8yCnuefBjut2PxEN0EFRBPv9P2wWlmOxkPKUq NrCJP0rDj5aONLrNZPrR8bZNdIShkZ/rKkoTuA0WMZ+xUlDRxAupdMkWAlrz 8IcwNcdDjPnkGObpN5Ctm3vK7UGSBmPeNqkXOYf3QTJ9gStJEd0F6+DzTN5C KGt1IyuGwZqL2Yk51FDIIkr9ykEnBMaA39LS7GFHEDNGlW+fKC7AzA0zfoOr fXZlHMBuqHtXqk3zrsHRqGGoocigg4ctrhD1UREYKj+eIj1TBiRdf7c6+COf NIOmej8pX3FmZ4ui+dDA8r2ctgsWHrb4A6iiH+v1DRA61GtoaA/tNRggewXW VXCZCGWyyTuyHGOqq5ozrv5MlzZLWD/KV/uDsAWmy20RAed1C4AzcXlpX25O M4SNl47g5VRNJRtMqokc8j6TjZrzMDEwITAJBgUrDgMCGgUABBRrkIRuS5qg BC8fv38mue8LZVcbHQQIUNrWKEnskCoCAggA EOF p12 = OpenSSL::PKCS12.new(str, "abc123") assert_equal Fixtures.pkey("rsa-1").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", @mykey, @mycert, nil, DEFAULT_PBE_PKEYS, DEFAULT_PBE_CERTS, ) assert_equal p12.to_der, p12.dup.to_der end def test_set_mac_pkcs12kdf p12 = OpenSSL::PKCS12.create( "pass", "name", @mykey, @mycert, nil, nil, nil, nil, 1234, # mac_iter nil, ) macdata = macdata(p12) # Depends on the OpenSSL version: SHA256 in OpenSSL >= 3.0 assert_include ["SHA1", "SHA256"], macdata[:mac_algo] assert_equal 1234, macdata[:iter] p12.set_mac("pass", "macsalt", 2345, "SHA384") macdata = macdata(p12) assert_equal "SHA384", macdata[:mac_algo] assert_equal "macsalt", macdata[:salt] assert_equal 2345, macdata[:iter] assert_equal @mykey.to_der, OpenSSL::PKCS12.new(p12.to_der, "pass").key.to_der end private def macdata(p12) # See RFC 7292 asn1 = OpenSSL::ASN1.decode(p12.to_der) macdata = asn1.value[2] mac = macdata.value[0] mac_algo = mac.value[0].value[0].value _mac_params = mac.value[0].value[1] { mac_algo: mac_algo, salt: macdata.value[1].value, iter: macdata.value[2]&.value, } end end end end ================================================ FILE: test/openssl/test_pkcs7.rb ================================================ # frozen_string_literal: true require_relative 'utils' if defined?(OpenSSL) class OpenSSL::TestPKCS7 < OpenSSL::TestCase def setup super @ca_key = Fixtures.pkey("rsa-1") @ee1_key = Fixtures.pkey("rsa-2") @ee2_key = Fixtures.pkey("rsa-3") ca = OpenSSL::X509::Name.new([["CN", "CA"]]) ee1 = OpenSSL::X509::Name.new([["CN", "EE1"]]) ee2 = OpenSSL::X509::Name.new([["CN", "EE2"]]) ca_exts = [ ["basicConstraints", "CA:TRUE", true], ["keyUsage", "keyCertSign, cRLSign", true], ["subjectKeyIdentifier", "hash", false], ["authorityKeyIdentifier", "keyid:always", false], ] @ca_cert = issue_cert(ca, @ca_key, 1, ca_exts, nil, nil) ee_exts = [ ["keyUsage", "nonRepudiation, digitalSignature, keyEncipherment", true], ["authorityKeyIdentifier", "keyid:always", false], ["extendedKeyUsage", "clientAuth, emailProtection, codeSigning", false], ] @ee1_cert = issue_cert(ee1, @ee1_key, 2, ee_exts, @ca_cert, @ca_key) @ee2_cert = issue_cert(ee2, @ee2_key, 3, ee_exts, @ca_cert, @ca_key) end def test_signed store = OpenSSL::X509::Store.new store.add_cert(@ca_cert) data = "aaaaa\nbbbbb\nccccc\n" ca_certs = [@ca_cert] tmp = OpenSSL::PKCS7.sign(@ee1_cert, @ee1_key, data, ca_certs) # TODO: #data contains untranslated content assert_equal("aaaaa\nbbbbb\nccccc\n", tmp.data) assert_nil(tmp.error_string) p7 = OpenSSL::PKCS7.new(tmp.to_der) assert_nil(p7.data) assert_nil(p7.error_string) assert_true(p7.verify([], store)) # AWS-LC does not appear to convert to CRLF automatically assert_equal("aaaaa\r\nbbbbb\r\nccccc\r\n", p7.data) unless aws_lc? assert_nil(p7.error_string) certs = p7.certificates assert_equal(2, certs.size) assert_equal(@ee1_cert.subject, certs[0].subject) assert_equal(@ca_cert.subject, certs[1].subject) signers = p7.signers assert_equal(1, signers.size) assert_equal(@ee1_cert.serial, signers[0].serial) assert_equal(@ee1_cert.issuer, signers[0].issuer) # AWS-LC does not generate authenticatedAttributes assert_in_delta(Time.now, signers[0].signed_time, 10) unless aws_lc? assert_false(p7.verify([@ca_cert], OpenSSL::X509::Store.new)) end def test_signed_flags store = OpenSSL::X509::Store.new store.add_cert(@ca_cert) # 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. # # PKCS7::NOATTR flag suppresses authenticatedAttributes. data = "aaaaa\nbbbbb\nccccc\n" flag = OpenSSL::PKCS7::BINARY | OpenSSL::PKCS7::NOATTR tmp = OpenSSL::PKCS7.sign(@ee1_cert, @ee1_key, data, [@ca_cert], flag) p7 = OpenSSL::PKCS7.new(tmp.to_der) assert_true(p7.verify([], store)) assert_equal(data, p7.data) certs = p7.certificates assert_equal(2, certs.size) assert_equal(@ee1_cert.subject, certs[0].subject) assert_equal(@ca_cert.subject, certs[1].subject) signers = p7.signers assert_equal(1, signers.size) assert_equal(@ee1_cert.serial, signers[0].serial) assert_equal(@ee1_cert.issuer, signers[0].issuer) assert_raise(OpenSSL::PKCS7::PKCS7Error) { signers[0].signed_time } end def test_signed_multiple_signers store = OpenSSL::X509::Store.new store.add_cert(@ca_cert) # 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 data = "aaaaa\r\nbbbbb\r\nccccc\r\n" tmp1 = OpenSSL::PKCS7.sign(@ee1_cert, @ee1_key, data) tmp2 = OpenSSL::PKCS7.sign(@ee2_cert, @ee2_key, data) tmp1.add_signer(tmp2.signers[0]) tmp1.add_certificate(@ee2_cert) p7 = OpenSSL::PKCS7.new(tmp1.to_der) assert_true(p7.verify([], store)) assert_equal(data, p7.data) certs = p7.certificates assert_equal(2, certs.size) signers = p7.signers assert_equal(2, signers.size) assert_equal(@ee1_cert.serial, signers[0].serial) assert_equal(@ee1_cert.issuer, signers[0].issuer) assert_equal(@ee2_cert.serial, signers[1].serial) assert_equal(@ee2_cert.issuer, signers[1].issuer) end def test_signed_add_signer data = "aaaaa\nbbbbb\nccccc\n" psi = OpenSSL::PKCS7::SignerInfo.new(@ee1_cert, @ee1_key, "sha256") p7 = OpenSSL::PKCS7.new p7.type = :signed p7.add_signer(psi) p7.add_certificate(@ee1_cert) p7.add_certificate(@ca_cert) p7.add_data(data) store = OpenSSL::X509::Store.new store.add_cert(@ca_cert) assert_equal(true, p7.verify([], store)) assert_equal(true, OpenSSL::PKCS7.new(p7.to_der).verify([], store)) assert_equal(1, p7.signers.size) end def test_detached_sign store = OpenSSL::X509::Store.new store.add_cert(@ca_cert) data = "aaaaa\nbbbbb\nccccc\n" ca_certs = [@ca_cert] flag = OpenSSL::PKCS7::BINARY|OpenSSL::PKCS7::DETACHED tmp = OpenSSL::PKCS7.sign(@ee1_cert, @ee1_key, data, ca_certs, flag) p7 = OpenSSL::PKCS7.new(tmp.to_der) assert_predicate(p7, :detached?) assert_true(p7.detached) assert_false(p7.verify([], store)) # FIXME: Should it be nil? assert_equal("", p7.data) assert_match(/no content|NO_CONTENT/, p7.error_string) assert_true(p7.verify([], store, data)) assert_equal(data, p7.data) assert_nil(p7.error_string) certs = p7.certificates assert_equal(2, certs.size) assert_equal(@ee1_cert.subject, certs[0].subject) assert_equal(@ca_cert.subject, certs[1].subject) signers = p7.signers assert_equal(1, signers.size) assert_equal(@ee1_cert.serial, signers[0].serial) assert_equal(@ee1_cert.issuer, signers[0].issuer) end def test_signed_authenticated_attributes # Using static PEM data because AWS-LC does not support generating one # with authenticatedAttributes. # # p7 was generated with OpenSSL 3.4.1 with this program with commandline # "faketime 2025-04-03Z ruby prog.rb": # # require_relative "test/openssl/utils" # include OpenSSL::TestUtils # key = Fixtures.pkey("p256") # cert = issue_cert(OpenSSL::X509::Name.new([["CN", "cert"]]), key, 1, [], nil, nil) # p7 = OpenSSL::PKCS7.sign(cert, key, "content", []) # puts p7.to_pem p7 = OpenSSL::PKCS7.new(<<~EOF) -----BEGIN PKCS7----- MIICvgYJKoZIhvcNAQcCoIICrzCCAqsCAQExDzANBglghkgBZQMEAgEFADAWBgkq hkiG9w0BBwGgCQQHY29udGVudKCCAQ4wggEKMIGxoAMCAQICAQEwCgYIKoZIzj0E AwIwDzENMAsGA1UEAwwEY2VydDAeFw0yNTA0MDIyMzAwMDFaFw0yNTA0MDMwMTAw MDFaMA8xDTALBgNVBAMMBGNlcnQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQW CWTZz6hVQgpDrh5kb1uEs09YHuVJn8CsrjV4bLnADNT/QbnVe20J4FSX4xqFm2f1 87Ukp0XiomZLf11eekQ2MAoGCCqGSM49BAMCA0gAMEUCIEg1fDI8b3hZAArgniVk HeM6puwgcMh5NXwvJ9x0unVmAiEAppecVTSQ+yEPyBG415Og6sK+RC78pcByEC81 C/QSwRYxggFpMIIBZQIBATAUMA8xDTALBgNVBAMMBGNlcnQCAQEwDQYJYIZIAWUD BAIBBQCggeQwGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUx DxcNMjUwNDAzMDAwMDAxWjAvBgkqhkiG9w0BCQQxIgQg7XACtDnprIRfIjV9gius FERzD722AW0+yUMil7nsn3MweQYJKoZIhvcNAQkPMWwwajALBglghkgBZQMEASow CwYJYIZIAWUDBAEWMAsGCWCGSAFlAwQBAjAKBggqhkiG9w0DBzAOBggqhkiG9w0D AgICAIAwDQYIKoZIhvcNAwICAUAwBwYFKw4DAgcwDQYIKoZIhvcNAwICASgwCgYI KoZIzj0EAwIESDBGAiEAssymc28HySAhg+XeWIpSbtzkwycr2JG6dzHRZ+vn0ocC IQCJVpo1FTLZOHSc9UpjS+VKR4cg50Iz0HiPyo6hwjCrwA== -----END PKCS7----- EOF cert = p7.certificates[0] store = OpenSSL::X509::Store.new.tap { |store| store.time = Time.utc(2025, 4, 3) store.add_cert(cert) } assert_equal(true, p7.verify([], store)) assert_equal(1, p7.signers.size) signer = p7.signers[0] assert_in_delta(Time.utc(2025, 4, 3), signer.signed_time, 10) end def test_enveloped omit_on_fips # PKCS #1 v1.5 padding 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, recip[0].issuer) assert_equal(@ee1_cert.serial, recip[0].serial) assert_equal(16, @ee1_key.decrypt(recip[0].enc_key).size) assert_equal(data, p7.decrypt(@ee1_key, @ee1_cert)) assert_equal(@ca_cert.subject, recip[1].issuer) assert_equal(@ee2_cert.serial, recip[1].serial) assert_equal(data, p7.decrypt(@ee2_key, @ee2_cert)) assert_equal(data, p7.decrypt(@ee1_key)) assert_raise(OpenSSL::PKCS7::PKCS7Error) { p7.decrypt(@ca_key, @ca_cert) } # Default cipher has been removed in v3.3 assert_raise_with_message(ArgumentError, /RC2-40-CBC/) { OpenSSL::PKCS7.encrypt(certs, data) } end def test_enveloped_add_recipient omit_on_fips # PKCS #1 v1.5 padding data = "aaaaa\nbbbbb\nccccc\n" ktri_ee1 = OpenSSL::PKCS7::RecipientInfo.new(@ee1_cert) ktri_ee2 = OpenSSL::PKCS7::RecipientInfo.new(@ee2_cert) tmp = OpenSSL::PKCS7.new tmp.type = :enveloped tmp.cipher = "AES-128-CBC" tmp.add_recipient(ktri_ee1) tmp.add_recipient(ktri_ee2) tmp.add_data(data) p7 = OpenSSL::PKCS7.new(tmp.to_der) assert_equal(:enveloped, p7.type) assert_equal(data, p7.decrypt(@ee1_key, @ee1_cert)) assert_equal(data, p7.decrypt(@ee2_key, @ee2_cert)) assert_equal([@ee1_cert.serial, @ee2_cert.serial].sort, p7.recipients.map(&:serial).sort) end def test_data asn1 = OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::ObjectId("pkcs7-data"), OpenSSL::ASN1::OctetString("content", 0, :EXPLICIT), ]) p7 = OpenSSL::PKCS7.new p7.type = :data p7.data = "content" assert_raise(OpenSSL::PKCS7::PKCS7Error) { p7.add_certificate(@ee1_cert) } assert_raise(OpenSSL::PKCS7::PKCS7Error) { p7.certificates = [@ee1_cert] } assert_raise(OpenSSL::PKCS7::PKCS7Error) { p7.cipher = "aes-128-cbc" } assert_equal(asn1.to_der, p7.to_der) p7 = OpenSSL::PKCS7.new(asn1) assert_equal(:data, p7.type) assert_equal(false, p7.detached) assert_equal(false, p7.detached?) # Not applicable assert_nil(p7.certificates) assert_nil(p7.crls) # Not applicable. Should they return nil or raise an exception instead? assert_equal([], p7.signers) assert_equal([], p7.recipients) # PKCS7#verify can't distinguish verification failure and other errors store = OpenSSL::X509::Store.new assert_equal(false, p7.verify([@ee1_cert], store)) assert_match(/wrong content type|WRONG_CONTENT_TYPE/, p7.error_string) assert_raise(OpenSSL::PKCS7::PKCS7Error) { p7.decrypt(@ee1_key) } end def test_empty_signed_data_ruby_bug_19974 data = "-----BEGIN PKCS7-----\nMAsGCSqGSIb3DQEHAg==\n-----END PKCS7-----\n" assert_raise(OpenSSL::PKCS7::PKCS7Error) { OpenSSL::PKCS7.new(data) } data = < "secp384r1", }) assert_instance_of OpenSSL::PKey::EC, pkey assert_equal "secp384r1", pkey.group.curve_name assert_equal nil, pkey.private_key # Invalid options are checked assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.generate_parameters("EC", "invalid" => "option") } end def test_s_generate_parameters_with_block # DSA kengen is not FIPS-approved. # https://github.com/openssl/openssl/commit/49a35f0#diff-605396c063194975af8ce31399d42690ab18186b422fb5012101cc9132660fe1R611-R614 omit_on_fips # Parameter generation callback is called if openssl?(3, 0, 0, 0) && !openssl?(3, 0, 0, 6) # Errors in BN_GENCB were not properly handled. This special pend is to # suppress failures on Ubuntu 22.04, which uses OpenSSL 3.0.2. pend "unstable test on OpenSSL 3.0.[0-5]" end cb_called = [] assert_raise(RuntimeError) { OpenSSL::PKey.generate_parameters("DSA") { |*args| cb_called << args raise "exit!" if cb_called.size == 3 } } assert_not_empty cb_called end def test_s_generate_key assert_raise(OpenSSL::PKey::PKeyError) { # DSA key pair cannot be generated without parameters OpenSSL::PKey.generate_key("DSA") } pkey_params = OpenSSL::PKey.generate_parameters("EC", { "ec_paramgen_curve" => "secp384r1", }) pkey = OpenSSL::PKey.generate_key(pkey_params) assert_instance_of OpenSSL::PKey::EC, pkey assert_equal "secp384r1", pkey.group.curve_name assert_not_equal nil, pkey.private_key end def test_s_read_pem_unknown_block # A PEM-encoded certificate and a PEM-encoded private key are combined. # Check that OSSL_STORE doesn't stop after the first PEM block. orig = Fixtures.pkey("rsa-1") subject = OpenSSL::X509::Name.new([["CN", "test"]]) cert = issue_cert(subject, orig, 1, [], nil, nil) input = cert.to_text + cert.to_pem + orig.to_text + orig.private_to_pem pkey = OpenSSL::PKey.read(input) assert_equal(orig.private_to_der, pkey.private_to_der) end def test_s_read_der_then_pem # If the input is valid as both DER and PEM (which allows garbage data # before and after the block), it is read as DER # # TODO: Garbage data after DER should not be allowed, but it is currently # ignored orig1 = Fixtures.pkey("rsa-1") orig2 = Fixtures.pkey("rsa-2") pkey = OpenSSL::PKey.read(orig1.public_to_der + orig2.private_to_pem) assert_equal(orig1.public_to_der, pkey.public_to_der) assert_not_predicate(pkey, :private?) end def test_s_read_passphrase orig = Fixtures.pkey("rsa-1") encrypted_pem = orig.private_to_pem("AES-256-CBC", "correct_passphrase") assert_match(/\A-----BEGIN ENCRYPTED PRIVATE KEY-----/, encrypted_pem) # Correct passphrase passed as the second argument pkey1 = OpenSSL::PKey.read(encrypted_pem, "correct_passphrase") assert_equal(orig.private_to_der, pkey1.private_to_der) # Correct passphrase returned by the block. The block gets false called = 0 flag = nil pkey2 = OpenSSL::PKey.read(encrypted_pem) { |f| called += 1 flag = f "correct_passphrase" } assert_equal(orig.private_to_der, pkey2.private_to_der) assert_equal(1, called) assert_false(flag) # Incorrect passphrase passed. The block is not called called = 0 assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.read(encrypted_pem, "incorrect_passphrase") { called += 1 } } assert_equal(0, called) # Incorrect passphrase returned by the block. The block is called only once called = 0 assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.read(encrypted_pem) { called += 1 "incorrect_passphrase" } } assert_equal(1, called) end def test_s_read_passphrase_tty omit "https://github.com/aws/aws-lc/pull/2555" if aws_lc? orig = Fixtures.pkey("rsa-1") encrypted_pem = orig.private_to_pem("AES-256-CBC", "correct_passphrase") # Correct passphrase passed to OpenSSL's prompt script = <<~"end;" require "openssl" Process.setsid OpenSSL::PKey.read(#{encrypted_pem.dump}) puts "ok" end; assert_in_out_err([*$:.map { |l| "-I#{l}" }, "-e#{script}"], "correct_passphrase\n") { |stdout, stderr| assert_equal(["Enter PEM pass phrase:"], stderr) assert_equal(["ok"], stdout) } # Incorrect passphrase passed to OpenSSL's prompt script = <<~"end;" require "openssl" Process.setsid begin OpenSSL::PKey.read(#{encrypted_pem.dump}) rescue OpenSSL::PKey::PKeyError puts "ok" else puts "expected OpenSSL::PKey::PKeyError" end end; stdin = "incorrect_passphrase\n" * 5 assert_in_out_err([*$:.map { |l| "-I#{l}" }, "-e#{script}"], stdin) { |stdout, stderr| assert_equal(1, stderr.count("Enter PEM pass phrase:")) assert_equal(["ok"], stdout) } end if ENV["OSSL_TEST_ALL"] == "1" && Process.respond_to?(:setsid) def test_hmac_sign_verify pkey = OpenSSL::PKey.generate_key("HMAC", { "key" => "abcd" }) hmac = OpenSSL::HMAC.new("abcd", "SHA256").update("data").digest assert_equal hmac, pkey.sign("SHA256", "data") # EVP_PKEY_HMAC does not support verify assert_raise(OpenSSL::PKey::PKeyError) { pkey.verify("SHA256", "data", hmac) } end def test_ed25519 # Ed25519 is not FIPS-approved. omit_on_fips # Test vector from RFC 8032 Section 7.1 TEST 2 priv_pem = <<~EOF -----BEGIN PRIVATE KEY----- MC4CAQAwBQYDK2VwBCIEIEzNCJso/5banbbDRuwRTg9bijGfNaumJNqM9u1PuKb7 -----END PRIVATE KEY----- EOF pub_pem = <<~EOF -----BEGIN PUBLIC KEY----- MCowBQYDK2VwAyEAPUAXw+hDiVqStwqnTRt+vJyYLM8uxJaMwM1V8Sr0Zgw= -----END PUBLIC KEY----- EOF priv = OpenSSL::PKey.read(priv_pem) pub = OpenSSL::PKey.read(pub_pem) assert_instance_of OpenSSL::PKey::PKey, priv assert_instance_of OpenSSL::PKey::PKey, pub assert_equal priv_pem, priv.private_to_pem assert_equal pub_pem, priv.public_to_pem assert_equal pub_pem, pub.public_to_pem assert_equal "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb", priv.raw_private_key.unpack1("H*") assert_equal OpenSSL::PKey.new_raw_private_key("ED25519", priv.raw_private_key).private_to_pem, priv.private_to_pem assert_equal "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c", priv.raw_public_key.unpack1("H*") assert_equal OpenSSL::PKey.new_raw_public_key("ED25519", priv.raw_public_key).public_to_pem, pub.public_to_pem sig = [<<~EOF.gsub(/[^0-9a-f]/, "")].pack("H*") 92a009a9f0d4cab8720e820b5f642540 a2b27b5416503f8fb3762223ebdb69da 085ac1e43e15996e458f3613d0f11d8c 387b2eaeb4302aeeb00d291612bb0c00 EOF data = ["72"].pack("H*") assert_equal sig, priv.sign(nil, data) assert_equal true, priv.verify(nil, sig, data) assert_equal true, pub.verify(nil, sig, data) assert_equal false, pub.verify(nil, sig, data.succ) # PureEdDSA wants nil as the message digest assert_raise(OpenSSL::PKey::PKeyError) { priv.sign("SHA512", data) } assert_raise(OpenSSL::PKey::PKeyError) { pub.verify("SHA512", sig, data) } # Ed25519 pkey type does not support key derivation assert_raise(OpenSSL::PKey::PKeyError) { priv.derive(pub) } end def test_x25519 omit_on_fips # Test vector from RFC 7748 Section 6.1 alice_pem = <<~EOF -----BEGIN PRIVATE KEY----- MC4CAQAwBQYDK2VuBCIEIHcHbQpzGKV9PBbBclGyZkXfTC+H68CZKrF3+6UduSwq -----END PRIVATE KEY----- EOF bob_pem = <<~EOF -----BEGIN PUBLIC KEY----- MCowBQYDK2VuAyEA3p7bfXt9wbTTW2HC7OQ1Nz+DQ8hbeGdNrfx+FG+IK08= -----END PUBLIC KEY----- EOF shared_secret = "4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742" alice = OpenSSL::PKey.read(alice_pem) bob = OpenSSL::PKey.read(bob_pem) assert_instance_of OpenSSL::PKey::PKey, alice assert_equal "X25519", alice.oid assert_match %r{oid=X25519}, alice.inspect assert_equal alice_pem, alice.private_to_pem assert_equal bob_pem, bob.public_to_pem assert_equal [shared_secret].pack("H*"), alice.derive(bob) alice_private = OpenSSL::PKey.new_raw_private_key("X25519", alice.raw_private_key) bob_public = OpenSSL::PKey.new_raw_public_key("X25519", bob.raw_public_key) assert_equal alice_private.private_to_pem, alice.private_to_pem assert_equal bob_public.public_to_pem, bob.public_to_pem assert_equal "77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a", alice.raw_private_key.unpack1("H*") assert_equal "de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f", bob.raw_public_key.unpack1("H*") end def test_ml_dsa # AWS-LC also supports ML-DSA, but it's implemented in a different way return unless openssl?(3, 5, 0) pkey = OpenSSL::PKey.generate_key("ML-DSA-44") assert_match(/type_name=ML-DSA-44/, pkey.inspect) sig = pkey.sign(nil, "data") assert_equal(2420, sig.bytesize) assert_equal(true, pkey.verify(nil, sig, "data")) pub2 = OpenSSL::PKey.read(pkey.public_to_der) assert_equal(true, pub2.verify(nil, sig, "data")) raw_public_key = pkey.raw_public_key assert_equal(1312, raw_public_key.bytesize) pub3 = OpenSSL::PKey.new_raw_public_key("ML-DSA-44", raw_public_key) assert_equal(true, pub3.verify(nil, sig, "data")) end def test_raw_initialize_errors assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.new_raw_private_key("foo123", "xxx") } assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.new_raw_private_key("ED25519", "xxx") } assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.new_raw_public_key("foo123", "xxx") } assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.new_raw_public_key("ED25519", "xxx") } end def test_compare? key1 = Fixtures.pkey("rsa-1") key2 = Fixtures.pkey("rsa-1") key3 = Fixtures.pkey("rsa-2") key4 = Fixtures.pkey("p256") assert_equal(true, key1.compare?(key2)) assert_equal(true, key1.public_key.compare?(key2)) assert_equal(true, key2.compare?(key1)) assert_equal(true, key2.public_key.compare?(key1)) assert_equal(false, key1.compare?(key3)) assert_raise(TypeError) do key1.compare?(key4) end end def test_to_text rsa = Fixtures.pkey("rsa-1") assert_include rsa.to_text, "publicExponent" end def test_legacy_error_classes assert_same(OpenSSL::PKey::PKeyError, OpenSSL::PKey::DSAError) assert_same(OpenSSL::PKey::PKeyError, OpenSSL::PKey::DHError) assert_same(OpenSSL::PKey::PKeyError, OpenSSL::PKey::ECError) assert_same(OpenSSL::PKey::PKeyError, OpenSSL::PKey::RSAError) end end ================================================ FILE: test/openssl/test_pkey_dh.rb ================================================ # frozen_string_literal: true require_relative 'utils' if defined?(OpenSSL) && defined?(OpenSSL::PKey::DH) class OpenSSL::TestPKeyDH < OpenSSL::PKeyTestCase def test_new_empty # pkeys are immutable with OpenSSL >= 3.0 if openssl?(3, 0, 0) assert_raise(ArgumentError) { OpenSSL::PKey::DH.new } else dh = OpenSSL::PKey::DH.new assert_nil(dh.p) assert_nil(dh.priv_key) end end def test_new_generate begin dh1 = OpenSSL::PKey::DH.new(512) rescue OpenSSL::PKey::PKeyError omit "generating 512-bit DH parameters failed; " \ "likely not supported by this OpenSSL build" end assert_equal(512, dh1.p.num_bits) assert_key(dh1) dh2 = OpenSSL::PKey::DH.generate(512) assert_equal(512, dh2.p.num_bits) assert_key(dh2) assert_not_equal(dh1.p, dh2.p) end if ENV["OSSL_TEST_ALL"] == "1" def test_new_break unless openssl? && OpenSSL.fips_mode assert_raise(RuntimeError) do OpenSSL::PKey::DH.new(2048) { raise } end else # The block argument is not executed in FIPS case. # See https://github.com/ruby/openssl/issues/692 for details. assert_kind_of(OpenSSL::PKey::DH, OpenSSL::PKey::DH.new(2048) { raise }) end end def test_derive_key params = Fixtures.pkey("dh2048_ffdhe2048") dh1 = OpenSSL::PKey.generate_key(params) dh2 = OpenSSL::PKey.generate_key(params) dh1_pub = OpenSSL::PKey.read(dh1.public_to_der) dh2_pub = OpenSSL::PKey.read(dh2.public_to_der) z = dh1.g.mod_exp(dh1.priv_key, dh1.p).mod_exp(dh2.priv_key, dh1.p).to_s(2) assert_equal z, dh1.derive(dh2_pub) assert_equal z, dh2.derive(dh1_pub) assert_raise(OpenSSL::PKey::PKeyError) { params.derive(dh1_pub) } assert_raise(OpenSSL::PKey::PKeyError) { dh1_pub.derive(params) } assert_equal z, dh1.compute_key(dh2.pub_key) assert_equal z, dh2.compute_key(dh1.pub_key) end def test_DHparams dh_params = Fixtures.pkey("dh2048_ffdhe2048") asn1 = OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Integer(dh_params.p), OpenSSL::ASN1::Integer(dh_params.g) ]) assert_equal(asn1.to_der, dh_params.to_der) key = OpenSSL::PKey::DH.new(asn1.to_der) assert_same_dh_params(dh_params, key) pem = <<~EOF -----BEGIN DH PARAMETERS----- MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz +8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a 87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7 YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi 7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg== -----END DH PARAMETERS----- EOF assert_equal(pem, dh_params.export) key = OpenSSL::PKey::DH.new(pem) assert_same_dh_params(dh_params, key) assert_no_key(key) key = OpenSSL::PKey.read(pem) assert_same_dh_params(dh_params, key) assert_no_key(key) key = OpenSSL::PKey.generate_key(dh_params) assert_same_dh_params(dh_params, key) assert_key(key) assert_equal(dh_params.to_der, key.to_der) assert_equal(dh_params.to_pem, key.to_pem) end def test_public_key dh = Fixtures.pkey("dh2048_ffdhe2048") 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 # Deprecated in v3.0.0; incompatible with OpenSSL 3.0 dh = Fixtures.pkey("dh2048_ffdhe2048") assert_no_key(dh) dh.generate_key! assert_key(dh) dh2 = OpenSSL::PKey::DH.new(dh.to_der) dh2.generate_key! assert_not_equal(dh.pub_key, dh2.pub_key) assert_equal(dh.compute_key(dh2.pub_key), dh2.compute_key(dh.pub_key)) end if !openssl?(3, 0, 0) def test_params_ok? omit_on_fips # Skip the tests in old OpenSSL version 1.1.1c or early versions before # applying the following commits in OpenSSL 1.1.1d to make `DH_check` # function pass the RFC 7919 FFDHE group texts. # https://github.com/openssl/openssl/pull/9435 if openssl? && !openssl?(1, 1, 1, 4) pend 'DH check for RFC 7919 FFDHE group texts is not implemented' end dh0 = Fixtures.pkey("dh2048_ffdhe2048") dh1 = OpenSSL::PKey::DH.new(OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Integer(dh0.p), OpenSSL::ASN1::Integer(dh0.g) ])) assert_equal(true, dh1.params_ok?) # AWS-LC automatically does parameter checks on the parsed params. if aws_lc? assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey::DH.new(OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Integer(dh0.p + 1), OpenSSL::ASN1::Integer(dh0.g) ])) } else dh2 = OpenSSL::PKey::DH.new(OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Integer(dh0.p + 1), OpenSSL::ASN1::Integer(dh0.g) ])) assert_equal(false, dh2.params_ok?) end end def test_params dh = Fixtures.pkey("dh2048_ffdhe2048") assert_kind_of(OpenSSL::BN, dh.p) assert_equal(dh.p, dh.params["p"]) assert_kind_of(OpenSSL::BN, dh.g) assert_equal(dh.g, dh.params["g"]) assert_nil(dh.pub_key) assert_nil(dh.params["pub_key"]) assert_nil(dh.priv_key) assert_nil(dh.params["priv_key"]) dhkey = OpenSSL::PKey.generate_key(dh) assert_equal(dh.params["p"], dhkey.params["p"]) assert_kind_of(OpenSSL::BN, dhkey.pub_key) assert_equal(dhkey.pub_key, dhkey.params["pub_key"]) assert_kind_of(OpenSSL::BN, dhkey.priv_key) assert_equal(dhkey.priv_key, dhkey.params["priv_key"]) end def test_dup # Parameters only dh1 = Fixtures.pkey("dh2048_ffdhe2048") dh2 = dh1.dup assert_equal dh1.to_der, dh2.to_der assert_not_equal nil, dh1.p assert_not_equal nil, dh1.g assert_equal [dh1.p, dh1.g], [dh2.p, dh2.g] assert_equal nil, dh1.pub_key assert_equal nil, dh1.priv_key assert_equal [dh1.pub_key, dh1.priv_key], [dh2.pub_key, dh2.priv_key] # PKey is immutable in OpenSSL >= 3.0 if !openssl?(3, 0, 0) dh2.set_pqg(dh2.p + 1, nil, dh2.g) assert_not_equal dh2.p, dh1.p end # With a key pair dh3 = OpenSSL::PKey.generate_key(Fixtures.pkey("dh2048_ffdhe2048")) dh4 = dh3.dup assert_equal dh3.to_der, dh4.to_der assert_equal dh1.to_der, dh4.to_der # encodes parameters only assert_equal [dh1.p, dh1.g], [dh4.p, dh4.g] assert_not_equal nil, dh3.pub_key assert_not_equal nil, dh3.priv_key assert_equal [dh3.pub_key, dh3.priv_key], [dh4.pub_key, dh4.priv_key] end def test_marshal dh = Fixtures.pkey("dh2048_ffdhe2048") deserialized = Marshal.load(Marshal.dump(dh)) assert_equal dh.to_der, deserialized.to_der end private 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_true(dh.public?) assert_true(dh.private?) assert_kind_of(OpenSSL::BN, dh.pub_key) assert_kind_of(OpenSSL::BN, dh.priv_key) end def assert_same_dh_params(expected, key) check_component(expected, key, [:p, :q, :g]) end end end ================================================ FILE: test/openssl/test_pkey_dsa.rb ================================================ # frozen_string_literal: true require_relative 'utils' if defined?(OpenSSL) && defined?(OpenSSL::PKey::DSA) class OpenSSL::TestPKeyDSA < OpenSSL::PKeyTestCase def setup # May not be available in FIPS mode as DSA has been deprecated in FIPS 186-5 omit_on_fips end def test_private key = Fixtures.pkey("dsa2048") assert_equal true, key.private? key2 = OpenSSL::PKey::DSA.new(key.to_der) assert_equal true, key2.private? key3 = key.public_key assert_equal false, key3.private? key4 = OpenSSL::PKey::DSA.new(key3.to_der) assert_equal false, key4.private? end def test_new key = OpenSSL::PKey::DSA.new(2048) pem = key.public_key.to_pem OpenSSL::PKey::DSA.new pem end def test_new_break assert_nil(OpenSSL::PKey::DSA.new(2048) { break }) assert_raise(RuntimeError) do OpenSSL::PKey::DSA.new(2048) { raise } end end def test_new_empty # pkeys are immutable with OpenSSL >= 3.0 if openssl?(3, 0, 0) assert_raise(ArgumentError) { OpenSSL::PKey::DSA.new } else key = OpenSSL::PKey::DSA.new assert_nil(key.p) assert_raise(OpenSSL::PKey::PKeyError) { key.to_der } end end def test_generate # DSA.generate used to call DSA_generate_parameters_ex(), which adjusts the # size of q according to the size of p key1024 = OpenSSL::PKey::DSA.generate(1024) assert_predicate key1024, :private? assert_equal 1024, key1024.p.num_bits assert_equal 160, key1024.q.num_bits if ENV["OSSL_TEST_ALL"] == "1" # slow key2048 = OpenSSL::PKey::DSA.generate(2048) assert_equal 2048, key2048.p.num_bits assert_equal 256, key2048.q.num_bits key3072 = OpenSSL::PKey::DSA.generate(3072) assert_equal 3072, key3072.p.num_bits assert_equal 256, key3072.q.num_bits end end def test_sign_verify # The DSA valid size is 2048 or 3072 on FIPS. # https://github.com/openssl/openssl/blob/7649b5548e5c0352b91d9d3ed695e42a2ac1e99c/providers/common/securitycheck.c#L185-L188 dsa = Fixtures.pkey("dsa2048") data = "Sign me!" if defined?(OpenSSL::Digest::DSS1) signature = dsa.sign(OpenSSL::Digest.new('DSS1'), data) assert_equal true, dsa.verify(OpenSSL::Digest.new('DSS1'), signature, data) end signature = dsa.sign("SHA256", data) assert_equal true, dsa.verify("SHA256", signature, data) signature0 = (<<~'end;').unpack1("m") MD4CHQC0zmRkVOAHJTm28fS5PVUv+4LtBeNaKqr/yfmVAh0AsTcLqofWHoW8X5oWu8AOvngOcFVZ cLTvhY3XNw== end; assert_equal true, dsa.verify("SHA256", signature0, data) signature1 = signature0.succ assert_equal false, dsa.verify("SHA256", signature1, data) end def test_sign_verify_raw key = Fixtures.pkey("dsa2048") data = 'Sign me!' digest = OpenSSL::Digest.digest('SHA1', data) invalid_sig = key.sign_raw(nil, digest.succ) malformed_sig = "*" * invalid_sig.bytesize # Sign by #syssign sig = key.syssign(digest) assert_equal true, key.sysverify(digest, sig) assert_equal false, key.sysverify(digest, invalid_sig) assert_sign_verify_false_or_error { key.sysverify(digest, malformed_sig) } assert_equal true, key.verify_raw(nil, sig, digest) assert_equal false, key.verify_raw(nil, invalid_sig, digest) assert_sign_verify_false_or_error { key.verify_raw(nil, malformed_sig, digest) } # Sign by #sign_raw sig = key.sign_raw(nil, digest) assert_equal true, key.sysverify(digest, sig) assert_equal false, key.sysverify(digest, invalid_sig) assert_sign_verify_false_or_error { key.sysverify(digest, malformed_sig) } assert_equal true, key.verify_raw(nil, sig, digest) assert_equal false, key.verify_raw(nil, invalid_sig, digest) assert_sign_verify_false_or_error { key.verify_raw(nil, malformed_sig, digest) } end def test_DSAPrivateKey # OpenSSL DSAPrivateKey format; similar to RSAPrivateKey orig = Fixtures.pkey("dsa2048") asn1 = OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Integer(0), OpenSSL::ASN1::Integer(orig.p), OpenSSL::ASN1::Integer(orig.q), OpenSSL::ASN1::Integer(orig.g), OpenSSL::ASN1::Integer(orig.pub_key), OpenSSL::ASN1::Integer(orig.priv_key) ]) key = OpenSSL::PKey::DSA.new(asn1.to_der) assert_predicate key, :private? assert_same_dsa orig, key pem = der_to_pem(asn1.to_der, "DSA PRIVATE KEY") key = OpenSSL::PKey::DSA.new(pem) assert_same_dsa orig, key assert_equal asn1.to_der, orig.to_der assert_equal pem, orig.export end def test_DSAPrivateKey_encrypted # OpenSSL DSAPrivateKey with OpenSSL encryption orig = Fixtures.pkey("dsa2048") pem = der_to_encrypted_pem(orig.to_der, "DSA PRIVATE KEY", "abcdef") key = OpenSSL::PKey::DSA.new(pem, "abcdef") assert_same_dsa orig, key key = OpenSSL::PKey::DSA.new(pem) { "abcdef" } assert_same_dsa orig, key cipher = OpenSSL::Cipher.new("aes-128-cbc") exported = orig.to_pem(cipher, "abcdef\0\1") assert_same_dsa orig, OpenSSL::PKey::DSA.new(exported, "abcdef\0\1") assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey::DSA.new(exported, "abcdef") } end def test_PUBKEY orig = Fixtures.pkey("dsa2048") pub = OpenSSL::PKey::DSA.new(orig.public_to_der) asn1 = OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::ObjectId("DSA"), OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Integer(orig.p), OpenSSL::ASN1::Integer(orig.q), OpenSSL::ASN1::Integer(orig.g) ]) ]), OpenSSL::ASN1::BitString( OpenSSL::ASN1::Integer(orig.pub_key).to_der ) ]) key = OpenSSL::PKey::DSA.new(asn1.to_der) assert_not_predicate key, :private? assert_same_dsa pub, key pem = der_to_pem(asn1.to_der, "PUBLIC KEY") key = OpenSSL::PKey::DSA.new(pem) assert_same_dsa pub, key assert_equal asn1.to_der, key.to_der assert_equal pem, key.export assert_equal asn1.to_der, orig.public_to_der assert_equal asn1.to_der, key.public_to_der assert_equal pem, orig.public_to_pem assert_equal pem, key.public_to_pem 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_params key = Fixtures.pkey("dsa2048") assert_kind_of(OpenSSL::BN, key.p) assert_equal(key.p, key.params["p"]) assert_kind_of(OpenSSL::BN, key.q) assert_equal(key.q, key.params["q"]) assert_kind_of(OpenSSL::BN, key.g) assert_equal(key.g, key.params["g"]) assert_kind_of(OpenSSL::BN, key.pub_key) assert_equal(key.pub_key, key.params["pub_key"]) assert_kind_of(OpenSSL::BN, key.priv_key) assert_equal(key.priv_key, key.params["priv_key"]) pubkey = OpenSSL::PKey.read(key.public_to_der) assert_equal(key.params["p"], pubkey.params["p"]) assert_equal(key.pub_key, pubkey.pub_key) assert_equal(key.pub_key, pubkey.params["pub_key"]) assert_nil(pubkey.priv_key) assert_nil(pubkey.params["priv_key"]) end def test_dup key = Fixtures.pkey("dsa2048") key2 = key.dup assert_equal key.params, key2.params # PKey is immutable in OpenSSL >= 3.0 if !openssl?(3, 0, 0) key2.set_pqg(key2.p + 1, key2.q, key2.g) assert_not_equal key.params, key2.params end end def test_marshal key = Fixtures.pkey("dsa2048") deserialized = Marshal.load(Marshal.dump(key)) assert_equal key.to_der, deserialized.to_der end private def assert_same_dsa(expected, key) check_component(expected, key, [:p, :q, :g, :pub_key, :priv_key]) end end end ================================================ FILE: test/openssl/test_pkey_ec.rb ================================================ # frozen_string_literal: true require_relative 'utils' if defined?(OpenSSL) class OpenSSL::TestEC < OpenSSL::PKeyTestCase def test_ec_key_new key1 = OpenSSL::PKey::EC.generate("prime256v1") 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 # PKey is immutable in OpenSSL >= 3.0; EC object should not be modified if !openssl?(3, 0, 0) key_tmp = OpenSSL::PKey::EC.generate("prime256v1") key5.private_key = key_tmp.private_key key5.public_key = key_tmp.public_key assert_not_equal key1.to_der, key5.to_der end end def test_ec_key_new_empty # pkeys are immutable with OpenSSL >= 3.0; constructing an empty EC object is # disallowed if openssl?(3, 0, 0) assert_raise(ArgumentError) { OpenSSL::PKey::EC.new } else key = OpenSSL::PKey::EC.new assert_nil(key.group) p256 = Fixtures.pkey("p256") key.group = p256.group key.private_key = p256.private_key key.public_key = p256.public_key assert_equal(p256.to_der, key.to_der) end end def test_builtin_curves builtin_curves = OpenSSL::PKey::EC.builtin_curves assert_not_empty builtin_curves assert_equal 2, builtin_curves[0].size assert_kind_of String, builtin_curves[0][0] assert_kind_of String, builtin_curves[0][1] builtin_curve_names = builtin_curves.map { |name, comment| name } assert_include builtin_curve_names, "prime256v1" end def test_generate assert_raise(OpenSSL::PKey::PKeyError) { 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_generate_key ec = OpenSSL::PKey::EC.new("prime256v1") assert_equal false, ec.private? assert_raise(OpenSSL::PKey::PKeyError) { ec.to_der } ec.generate_key! assert_equal true, ec.private? assert_nothing_raised { ec.to_der } end if !openssl?(3, 0, 0) def test_marshal key = Fixtures.pkey("p256") deserialized = Marshal.load(Marshal.dump(key)) assert_equal key.to_der, deserialized.to_der end def test_check_key omit_on_fips key0 = Fixtures.pkey("p256") assert_equal(true, key0.check_key) assert_equal(true, key0.private?) assert_equal(true, key0.public?) key1 = OpenSSL::PKey.read(key0.public_to_der) assert_equal(true, key1.check_key) assert_equal(false, key1.private?) assert_equal(true, key1.public?) key2 = OpenSSL::PKey.read(key0.private_to_der) assert_equal(true, key2.private?) assert_equal(true, key2.public?) assert_equal(true, key2.check_key) # Behavior of EVP_PKEY_public_check changes between OpenSSL 1.1.1 and 3.0 # The public key does not match the private key ec_key_data = <<~EOF -----BEGIN EC PRIVATE KEY----- MHcCAQEEIP+TT0V8Fndsnacji9tyf6hmhHywcOWTee9XkiBeJoVloAoGCCqGSM49 AwEHoUQDQgAEBkhhJIU/2/YdPSlY2I1k25xjK4trr5OXSgXvBC21PtY0HQ7lor7A jzT0giJITqmcd81fwGw5+96zLcdxTF1hVQ== -----END EC PRIVATE KEY----- EOF if aws_lc? # AWS-LC automatically does key checks on the parsed key. assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.read(ec_key_data) } else key4 = OpenSSL::PKey.read(ec_key_data) assert_raise(OpenSSL::PKey::PKeyError) { key4.check_key } end # EC#private_key= is deprecated in 3.0 and won't work on OpenSSL 3.0 if !openssl?(3, 0, 0) key2.private_key += 1 assert_raise(OpenSSL::PKey::PKeyError) { key2.check_key } end end def test_sign_verify p256 = Fixtures.pkey("p256") data = "Sign me!" signature = p256.sign("SHA256", data) assert_equal true, p256.verify("SHA256", signature, data) signature0 = (<<~'end;').unpack1("m") 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_derive_key # NIST CAVP, KAS_ECC_CDH_PrimitiveTest.txt, P-256 COUNT = 0 qCAVSx = "700c48f77f56584c5cc632ca65640db91b6bacce3a4df6b42ce7cc838833d287" qCAVSy = "db71e509e3fd9b060ddb20ba5c51dcc5948d46fbf640dfe0441782cab85fa4ac" dIUT = "7d7dc5f71eb29ddaf80d6214632eeae03d9058af1fb6d22ed80badb62bc1a534" zIUT = "46fc62106420ff012e54a434fbdd2d25ccc5852060561e68040dd7778997bd7b" a = OpenSSL::PKey::EC.new("prime256v1") a.private_key = OpenSSL::BN.new(dIUT, 16) b = OpenSSL::PKey::EC.new("prime256v1") uncompressed = OpenSSL::BN.new("04" + qCAVSx + qCAVSy, 16) b.public_key = OpenSSL::PKey::EC::Point.new(b.group, uncompressed) assert_equal [zIUT].pack("H*"), a.derive(b) assert_equal a.derive(b), a.dh_compute_key(b.public_key) end if !openssl?(3, 0, 0) # TODO: Test it without using #private_key= def test_sign_verify_raw key = Fixtures.pkey("p256") data1 = "foo" data2 = "bar" malformed_sig = "*" * 30 # Sign by #dsa_sign_asn1 sig = key.dsa_sign_asn1(data1) assert_equal true, key.dsa_verify_asn1(data1, sig) assert_equal false, key.dsa_verify_asn1(data2, sig) assert_sign_verify_false_or_error { key.dsa_verify_asn1(data1, malformed_sig) } assert_equal true, key.verify_raw(nil, sig, data1) assert_equal false, key.verify_raw(nil, sig, data2) assert_sign_verify_false_or_error { key.verify_raw(nil, malformed_sig, data1) } # Sign by #sign_raw sig = key.sign_raw(nil, data1) assert_equal true, key.dsa_verify_asn1(data1, sig) assert_equal false, key.dsa_verify_asn1(data2, sig) assert_sign_verify_false_or_error { key.dsa_verify_asn1(data1, malformed_sig) } assert_equal true, key.verify_raw(nil, sig, data1) assert_equal false, key.verify_raw(nil, sig, data2) assert_sign_verify_false_or_error{ key.verify_raw(nil, malformed_sig, data1) } end def test_dsa_sign_asn1_FIPS186_3 key = OpenSSL::PKey::EC.generate("prime256v1") size = key.group.order.num_bits / 8 + 1 dgst = (1..size).to_a.pack('C*') 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)) end def test_dh_compute_key key_a = OpenSSL::PKey::EC.generate("prime256v1") key_b = OpenSSL::PKey::EC.generate(key_a.group) 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 p256 = Fixtures.pkey("p256") asn1 = OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Integer(1), OpenSSL::ASN1::OctetString(p256.private_key.to_s(2)), OpenSSL::ASN1::ObjectId("prime256v1", 0, :EXPLICIT), OpenSSL::ASN1::BitString(p256.public_key.to_octet_string(:uncompressed), 1, :EXPLICIT) ]) 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_with_parameters p256 = Fixtures.pkey("p256") # The format used by "openssl ecparam -name prime256v1 -genkey -outform PEM" # # "EC PARAMETERS" block should be ignored if it is followed by an # "EC PRIVATE KEY" block in_pem = <<~EOF -----BEGIN EC PARAMETERS----- BggqhkjOPQMBBw== -----END EC PARAMETERS----- -----BEGIN EC PRIVATE KEY----- MHcCAQEEIID49FDqcf1O1eO8saTgG70UbXQw9Fqwseliit2aWhH1oAoGCCqGSM49 AwEHoUQDQgAEFglk2c+oVUIKQ64eZG9bhLNPWB7lSZ/ArK41eGy5wAzU/0G51Xtt CeBUl+MahZtn9fO1JKdF4qJmS39dXnpENg== -----END EC PRIVATE KEY----- EOF key = OpenSSL::PKey::EC.new(in_pem) assert_same_ec p256, key assert_equal p256.to_der, key.to_der end def test_ECPrivateKey_encrypted omit_on_fips p256 = Fixtures.pkey("p256") # 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::PKeyError) { OpenSSL::PKey::EC.new(exported, "abcdef") } end def test_PUBKEY p256 = Fixtures.pkey("p256") p256pub = OpenSSL::PKey::EC.new(p256.public_to_der) asn1 = OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::ObjectId("id-ecPublicKey"), OpenSSL::ASN1::ObjectId("prime256v1") ]), OpenSSL::ASN1::BitString( p256.public_key.to_octet_string(:uncompressed) ) ]) key = OpenSSL::PKey::EC.new(asn1.to_der) assert_not_predicate key, :private? assert_same_ec p256pub, 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 p256pub, key assert_equal asn1.to_der, key.to_der assert_equal pem, key.export assert_equal asn1.to_der, p256.public_to_der assert_equal asn1.to_der, key.public_to_der assert_equal pem, p256.public_to_pem assert_equal pem, key.public_to_pem 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 # AWS-LC does not support serializing explicit curves. unless aws_lc? assert_not_equal group1.to_der, group2.to_der end 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_octet_string(:uncompressed), group4.generator.to_octet_string(:uncompressed) 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_group_initialize_error_message # Test that passing 2 arguments raises the helpful error e = assert_raise(ArgumentError) do OpenSSL::PKey::EC::Group.new(:GFp, 123) end assert_equal("wrong number of arguments (given 2, expected 1 or 4)", e.message) end def test_ec_point group = OpenSSL::PKey::EC::Group.new("prime256v1") key = OpenSSL::PKey::EC.generate(group) 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 assert_equal point.to_octet_string(:uncompressed), point2.to_octet_string(:uncompressed) point3 = OpenSSL::PKey::EC::Point.new(group, point.to_octet_string(:uncompressed)) assert_equal point, point3 assert_equal point.to_bn, point3.to_bn assert_equal point.to_octet_string(:uncompressed), point3.to_octet_string(:uncompressed) point2.invert! point3.invert! assert_not_equal point.to_octet_string(:uncompressed), point2.to_octet_string(:uncompressed) assert_equal point2.to_octet_string(:uncompressed), point3.to_octet_string(:uncompressed) end def test_small_curve begin group = OpenSSL::PKey::EC::Group.new(:GFp, 17, 2, 2) group.point_conversion_form = :uncompressed generator = OpenSSL::PKey::EC::Point.new(group, B(%w{ 04 05 01 })) group.set_generator(generator, 19, 1) rescue OpenSSL::PKey::EC::Group::Error pend "Patched OpenSSL rejected curve" if /unsupported field/ =~ $!.message raise end assert_equal 17.to_bn.num_bits, group.degree assert_equal B(%w{ 04 05 01 }), group.generator.to_octet_string(:uncompressed) assert_equal 19.to_bn, group.order assert_equal 1.to_bn, group.cofactor assert_nil group.curve_name point = OpenSSL::PKey::EC::Point.new(group, B(%w{ 04 06 03 })) assert_equal 0x040603.to_bn, point.to_bn 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) group2 = group.dup; group2.point_conversion_form = :compressed point2 = OpenSSL::PKey::EC::Point.new(group2, B(%w{ 04 06 03 })) assert_equal 0x0306.to_bn, point2.to_bn assert_equal B(%w{ 04 06 03 }), point.to_octet_string(:uncompressed) assert_equal B(%w{ 03 06 }), point.to_octet_string(:compressed) assert_equal B(%w{ 07 06 03 }), point.to_octet_string(:hybrid) assert_equal true, point.on_curve? point.invert! # 8.5 assert_equal B(%w{ 04 06 0E }), point.to_octet_string(:uncompressed) 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 B(%w{ 00 }), point.to_octet_string(:uncompressed) assert_equal true, point.on_curve? end def test_ec_point_add begin group = OpenSSL::PKey::EC::Group.new(:GFp, 17, 2, 2) group.point_conversion_form = :uncompressed gen = OpenSSL::PKey::EC::Point.new(group, B(%w{ 04 05 01 })) group.set_generator(gen, 19, 1) point_a = OpenSSL::PKey::EC::Point.new(group, B(%w{ 04 06 03 })) point_b = OpenSSL::PKey::EC::Point.new(group, B(%w{ 04 10 0D })) rescue OpenSSL::PKey::EC::Group::Error pend "Patched OpenSSL rejected curve" if /unsupported field/ =~ $!.message raise end result = point_a.add(point_b) assert_equal B(%w{ 04 0D 07 }), result.to_octet_string(:uncompressed) assert_raise(TypeError) { point_a.add(nil) } assert_raise(ArgumentError) { point_a.add } 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, B(%w{ 04 05 01 })) group.set_generator(gen, 19, 1) # 3 * (6, 3) = (16, 13) point_a = OpenSSL::PKey::EC::Point.new(group, B(%w{ 04 06 03 })) result_a1 = point_a.mul(3) assert_equal B(%w{ 04 10 0D }), result_a1.to_octet_string(:uncompressed) # 3 * (6, 3) + 3 * (5, 1) = (7, 6) result_a2 = point_a.mul(3, 3) assert_equal B(%w{ 04 07 06 }), result_a2.to_octet_string(:uncompressed) 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 = Fixtures.pkey("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) } # mul with arrays was removed in version 4.0.0 assert_raise(NotImplementedError) { point.mul([1], []) } end # test Group: asn1_flag, point_conversion private def B(ary) [Array(ary).join].pack("H*") end def assert_same_ec(expected, key) check_component(expected, key, [:group, :public_key, :private_key]) end end end ================================================ FILE: test/openssl/test_pkey_rsa.rb ================================================ # frozen_string_literal: true require_relative "utils" if defined?(OpenSSL) class OpenSSL::TestPKeyRSA < OpenSSL::PKeyTestCase def test_no_private_exp key = OpenSSL::PKey::RSA.new rsa = Fixtures.pkey("rsa-1") key.set_key(rsa.n, rsa.e, nil) key.set_factors(rsa.p, rsa.q) assert_raise(OpenSSL::PKey::PKeyError){ key.private_encrypt("foo") } assert_raise(OpenSSL::PKey::PKeyError){ key.private_decrypt("foo") } end if !openssl?(3, 0, 0) # Impossible state in OpenSSL 3.0 def test_private key = Fixtures.pkey("rsa-1") # Generated by DER key2 = OpenSSL::PKey::RSA.new(key.to_der) assert_true(key2.private?) # public key key3 = key.public_key assert_false(key3.private?) # Generated by public key DER key4 = OpenSSL::PKey::RSA.new(key3.to_der) assert_false(key4.private?) if !openssl?(3, 0, 0) # Generated by RSA#set_key key5 = OpenSSL::PKey::RSA.new key5.set_key(key.n, key.e, key.d) assert_true(key5.private?) # Generated by RSA#set_key, without d key6 = OpenSSL::PKey::RSA.new key6.set_key(key.n, key.e, nil) assert_false(key6.private?) end end def test_new key = OpenSSL::PKey::RSA.new(2048) assert_equal 2048, key.n.num_bits assert_equal 65537, key.e assert_not_nil key.d assert(key.private?) end def test_new_public_exponent # At least 2024-bits RSA key are required in FIPS. omit_on_fips # Specify public exponent key = OpenSSL::PKey::RSA.new(512, 3) assert_equal 512, key.n.num_bits assert_equal 3, key.e end def test_new_empty # pkeys are immutable with OpenSSL >= 3.0 if openssl?(3, 0, 0) assert_raise(ArgumentError) { OpenSSL::PKey::RSA.new } else key = OpenSSL::PKey::RSA.new assert_nil(key.n) end end def test_s_generate key1 = OpenSSL::PKey::RSA.generate(2048) assert_equal 2048, key1.n.num_bits assert_equal 65537, key1.e end def test_s_generate_public_exponent # At least 2024-bits RSA key are required in FIPS. omit_on_fips # Specify public exponent key = OpenSSL::PKey::RSA.generate(512, 3) assert_equal 512, key.n.num_bits assert_equal 3, key.e end def test_new_break assert_nil(OpenSSL::PKey::RSA.new(2048) { break }) assert_raise(RuntimeError) do OpenSSL::PKey::RSA.new(2048) { raise } end end def test_sign_verify rsa = Fixtures.pkey("rsa2048") data = "Sign me!" signature = rsa.sign("SHA256", data) assert_equal true, rsa.verify("SHA256", signature, data) signature0 = (<<~'end;').unpack1("m") ooy49i8aeFtkDYUU0RPDsEugGiNw4lZxpbQPnIwtdftEkka945IqKZ/MY3YSw7wKsvBZeaTy8GqL lSWLThsRFDV+UUS9zUBbQ9ygNIT8OjdV+tNL63ZpKGprczSnw4F05MQIpajNRud/8jiI9rf+Wysi WwXecjMl2FlXlLJHY4PFQZU5TiametB4VCQRMcjLo1uf26u/yRpiGaYyqn5vxs0SqNtUDM1UL6x4 NHCAdqLjuFRQPjYp1vGLD3eSl4061pS8x1NVap3YGbYfGUyzZO4VfwFwf1jPdhp/OX/uZw4dGB2H gSK+q1JiDFwEE6yym5tdKovL1g1NhFYHF6gkZg== end; assert_equal true, rsa.verify("SHA256", signature0, data) signature1 = signature0.succ assert_equal false, rsa.verify("SHA256", signature1, data) end def test_sign_verify_options key = Fixtures.pkey("rsa2048") data = "Sign me!" pssopts = { "rsa_padding_mode" => "pss", "rsa_pss_saltlen" => 20, "rsa_mgf1_md" => "SHA256" } sig_pss = key.sign("SHA256", data, pssopts) assert_equal 256, sig_pss.bytesize assert_equal true, key.verify("SHA256", sig_pss, data, pssopts) assert_equal true, key.verify_pss("SHA256", sig_pss, data, salt_length: 20, mgf1_hash: "SHA256") # Defaults to PKCS #1 v1.5 padding => verification failure assert_equal false, key.verify("SHA256", sig_pss, data) # option type check assert_raise_with_message(TypeError, /expected Hash/) { key.sign("SHA256", data, ["x"]) } end def test_sign_verify_raw key = Fixtures.pkey("rsa-1") data = "Sign me!" hash = OpenSSL::Digest.digest("SHA256", data) signature = key.sign_raw("SHA256", hash) assert_equal true, key.verify_raw("SHA256", signature, hash) assert_equal true, key.verify("SHA256", signature, data) # Too long data assert_raise(OpenSSL::PKey::PKeyError) { key.sign_raw("SHA1", "x" * (key.n.num_bytes + 1)) } # With options pssopts = { "rsa_padding_mode" => "pss", "rsa_pss_saltlen" => 20, "rsa_mgf1_md" => "SHA256" } sig_pss = key.sign_raw("SHA256", hash, pssopts) assert_equal true, key.verify("SHA256", sig_pss, data, pssopts) assert_equal true, key.verify_raw("SHA256", sig_pss, hash, pssopts) end def test_sign_verify_raw_legacy key = Fixtures.pkey("rsa-1") bits = key.n.num_bits # Need right size for raw mode plain0 = "x" * (bits/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" * (bits/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::PKeyError){ key.private_encrypt(plain0, 666) } end def test_verify_empty_rsa rsa = OpenSSL::PKey::RSA.new assert_raise(OpenSSL::PKey::PKeyError, "[Bug #12783]") { rsa.verify("SHA1", "a", "b") } end unless openssl?(3, 0, 0) # Empty RSA is not possible with OpenSSL >= 3.0 def test_sign_verify_pss key = Fixtures.pkey("rsa2048") data = "Sign me!" invalid_data = "Sign me?" signature = key.sign_pss("SHA256", data, salt_length: 20, mgf1_hash: "SHA256") assert_equal 256, signature.bytesize assert_equal true, key.verify_pss("SHA256", signature, data, salt_length: 20, mgf1_hash: "SHA256") assert_equal true, key.verify_pss("SHA256", signature, data, salt_length: :auto, mgf1_hash: "SHA256") assert_equal false, key.verify_pss("SHA256", signature, invalid_data, salt_length: 20, mgf1_hash: "SHA256") signature = key.sign_pss("SHA256", data, salt_length: :digest, mgf1_hash: "SHA256") assert_equal true, key.verify_pss("SHA256", signature, data, salt_length: 32, mgf1_hash: "SHA256") assert_equal true, key.verify_pss("SHA256", signature, data, salt_length: :auto, mgf1_hash: "SHA256") assert_equal false, key.verify_pss("SHA256", signature, data, salt_length: 20, mgf1_hash: "SHA256") # The sign_pss with `salt_length: :max` raises the "invalid salt length" # error in FIPS. We need to skip the tests in FIPS. # According to FIPS 186-5 section 5.4, the salt length shall be between zero # and the output block length of the digest function (inclusive). # # FIPS 186-5 section 5.4 PKCS #1 # https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-5.pdf unless OpenSSL.fips_mode signature = key.sign_pss("SHA256", data, salt_length: :max, mgf1_hash: "SHA256") # Should verify on the following salt_length (sLen). # sLen <= emLen (octat) - 2 - hLen (octet) = 2048 / 8 - 2 - 256 / 8 = 222 # https://datatracker.ietf.org/doc/html/rfc8017#section-9.1.1 assert_equal true, key.verify_pss("SHA256", signature, data, salt_length: 222, mgf1_hash: "SHA256") assert_equal true, key.verify_pss("SHA256", signature, data, salt_length: :auto, mgf1_hash: "SHA256") end assert_raise(OpenSSL::PKey::PKeyError) { key.sign_pss("SHA256", data, salt_length: 223, mgf1_hash: "SHA256") } end def test_encrypt_decrypt rsapriv = Fixtures.pkey("rsa-1") rsapub = OpenSSL::PKey.read(rsapriv.public_to_der) # Defaults to PKCS #1 v1.5 raw = "data" # According to the NIST SP 800-131A Rev. 2 section 6, PKCS#1 v1.5 padding is # not permitted for key agreement and key transport using RSA in FIPS. # https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-131Ar2.pdf unless OpenSSL.fips_mode enc = rsapub.encrypt(raw) assert_equal raw, rsapriv.decrypt(enc) end # Invalid options assert_raise(OpenSSL::PKey::PKeyError) { rsapub.encrypt(raw, { "nonexistent" => "option" }) } end def test_encrypt_decrypt_legacy rsapriv = Fixtures.pkey("rsa-1") rsapub = OpenSSL::PKey.read(rsapriv.public_to_der) # Defaults to PKCS #1 v1.5 unless OpenSSL.fips_mode raw = "data" enc_legacy = rsapub.public_encrypt(raw) assert_equal raw, rsapriv.decrypt(enc_legacy) enc_new = rsapub.encrypt(raw) assert_equal raw, rsapriv.private_decrypt(enc_new) end # OAEP with default parameters raw = "data" enc_legacy = rsapub.public_encrypt(raw, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING) assert_equal raw, rsapriv.decrypt(enc_legacy, { "rsa_padding_mode" => "oaep" }) enc_new = rsapub.encrypt(raw, { "rsa_padding_mode" => "oaep" }) assert_equal raw, rsapriv.private_decrypt(enc_legacy, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING) end def test_export orig = Fixtures.pkey("rsa-1") pub = OpenSSL::PKey.read(orig.public_to_der) assert_not_equal orig.export, pub.export assert_equal orig.public_to_pem, pub.export # PKey is immutable in OpenSSL >= 3.0 if !openssl?(3, 0, 0) key = OpenSSL::PKey::RSA.new # key has only n, e and d key.set_key(orig.n, orig.e, orig.d) assert_equal orig.public_key.export, key.export # key has only n, e, d, p and q key.set_factors(orig.p, orig.q) assert_equal orig.public_key.export, key.export # key has n, e, d, p, q, dmp1, dmq1 and iqmp key.set_crt_params(orig.dmp1, orig.dmq1, orig.iqmp) assert_equal orig.export, key.export end end def test_to_der orig = Fixtures.pkey("rsa-1") pub = OpenSSL::PKey.read(orig.public_to_der) assert_not_equal orig.to_der, pub.to_der assert_equal orig.public_to_der, pub.to_der # PKey is immutable in OpenSSL >= 3.0 if !openssl?(3, 0, 0) key = OpenSSL::PKey::RSA.new # key has only n, e and d key.set_key(orig.n, orig.e, orig.d) assert_equal orig.public_key.to_der, key.to_der # key has only n, e, d, p and q key.set_factors(orig.p, orig.q) assert_equal orig.public_key.to_der, key.to_der # key has n, e, d, p, q, dmp1, dmq1 and iqmp key.set_crt_params(orig.dmp1, orig.dmq1, orig.iqmp) assert_equal orig.to_der, key.to_der end end def test_RSAPrivateKey rsa = Fixtures.pkey("rsa-1") asn1 = OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Integer(0), OpenSSL::ASN1::Integer(rsa.n), OpenSSL::ASN1::Integer(rsa.e), OpenSSL::ASN1::Integer(rsa.d), OpenSSL::ASN1::Integer(rsa.p), OpenSSL::ASN1::Integer(rsa.q), OpenSSL::ASN1::Integer(rsa.dmp1), OpenSSL::ASN1::Integer(rsa.dmq1), OpenSSL::ASN1::Integer(rsa.iqmp) ]) key = OpenSSL::PKey::RSA.new(asn1.to_der) assert_predicate key, :private? assert_same_rsa rsa, key pem = der_to_pem(asn1.to_der, "RSA PRIVATE KEY") key = OpenSSL::PKey::RSA.new(pem) assert_same_rsa rsa, key assert_equal asn1.to_der, rsa.to_der assert_equal pem, rsa.export # Unknown PEM prepended cert = issue_cert(OpenSSL::X509::Name.new([["CN", "nobody"]]), rsa, 1, [], nil, nil) str = cert.to_text + cert.to_pem + rsa.to_pem key = OpenSSL::PKey::RSA.new(str) assert_same_rsa rsa, key end def test_RSAPrivateKey_encrypted # PKCS #1 RSAPrivateKey with OpenSSL encryption omit_on_fips rsa = Fixtures.pkey("rsa2048") pem = der_to_encrypted_pem(rsa.to_der, "RSA PRIVATE KEY", "abcdef") key = OpenSSL::PKey::RSA.new(pem, "abcdef") assert_same_rsa rsa, key key = OpenSSL::PKey::RSA.new(pem) { "abcdef" } assert_same_rsa rsa, key cipher = OpenSSL::Cipher.new("aes-128-cbc") exported = rsa.to_pem(cipher, "abcdef\0\1") assert_same_rsa rsa, OpenSSL::PKey::RSA.new(exported, "abcdef\0\1") assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey::RSA.new(exported, "abcdef") } end def test_RSAPublicKey # PKCS #1 RSAPublicKey. Only decoding is supported orig = Fixtures.pkey("rsa-1") pub = OpenSSL::PKey::RSA.new(orig.public_to_der) asn1 = OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Integer(orig.n), OpenSSL::ASN1::Integer(orig.e) ]) key = OpenSSL::PKey::RSA.new(asn1.to_der) assert_not_predicate key, :private? assert_same_rsa pub, key pem = der_to_pem(asn1.to_der, "RSA PUBLIC KEY") key = OpenSSL::PKey::RSA.new(pem) assert_same_rsa pub, key end def test_PUBKEY orig = Fixtures.pkey("rsa-1") pub = OpenSSL::PKey::RSA.new(orig.public_to_der) asn1 = OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::ObjectId("rsaEncryption"), OpenSSL::ASN1::Null(nil) ]), OpenSSL::ASN1::BitString( OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Integer(orig.n), OpenSSL::ASN1::Integer(orig.e) ]).to_der ) ]) key = OpenSSL::PKey::RSA.new(asn1.to_der) assert_not_predicate key, :private? assert_same_rsa pub, key pem = der_to_pem(asn1.to_der, "PUBLIC KEY") key = OpenSSL::PKey::RSA.new(pem) assert_same_rsa pub, key assert_equal asn1.to_der, key.to_der assert_equal pem, key.export assert_equal asn1.to_der, orig.public_to_der assert_equal asn1.to_der, key.public_to_der assert_equal pem, orig.public_to_pem assert_equal pem, key.public_to_pem end def test_pem_passwd omit_on_fips key = Fixtures.pkey("rsa-1") pem3c = key.to_pem("aes-128-cbc", "key") assert_match (/ENCRYPTED/), pem3c assert_equal key.to_der, OpenSSL::PKey.read(pem3c, "key").to_der assert_equal key.to_der, OpenSSL::PKey.read(pem3c) { "key" }.to_der assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.read(pem3c) { nil } } end def test_private_encoding pkey = Fixtures.pkey("rsa-1") asn1 = OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Integer(0), OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::ObjectId("rsaEncryption"), OpenSSL::ASN1::Null(nil) ]), OpenSSL::ASN1::OctetString(pkey.to_der) ]) assert_equal asn1.to_der, pkey.private_to_der assert_same_rsa pkey, OpenSSL::PKey.read(asn1.to_der) pem = der_to_pem(asn1.to_der, "PRIVATE KEY") assert_equal pem, pkey.private_to_pem assert_same_rsa pkey, OpenSSL::PKey.read(pem) end def test_private_encoding_encrypted rsa = Fixtures.pkey("rsa2048") encoded = rsa.private_to_der("aes-128-cbc", "abcdefgh") asn1 = OpenSSL::ASN1.decode(encoded) # PKCS #8 EncryptedPrivateKeyInfo assert_kind_of OpenSSL::ASN1::Sequence, asn1 assert_equal 2, asn1.value.size assert_not_equal rsa.private_to_der, encoded assert_same_rsa rsa, OpenSSL::PKey.read(encoded, "abcdefgh") assert_same_rsa rsa, OpenSSL::PKey.read(encoded) { "abcdefgh" } assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.read(encoded, "abcxyz") } encoded = rsa.private_to_pem("aes-128-cbc", "abcdefgh") assert_match (/BEGIN ENCRYPTED PRIVATE KEY/), encoded.lines[0] assert_same_rsa rsa, OpenSSL::PKey.read(encoded, "abcdefgh") # Use openssl instead of certtool due to https://gitlab.com/gnutls/gnutls/-/issues/1632 # openssl pkcs8 -in test/openssl/fixtures/pkey/rsa2048.pem -topk8 -v2 aes-128-cbc -passout pass:abcdefgh pem = <<~EOF -----BEGIN ENCRYPTED PRIVATE KEY----- MIIFNTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQ+Sg92Hgy8EgVPf7t Hen1qwICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEAQIEEB5UX2xdDO8/AKA8 +Y5CZyUEggTQkArh4mMPpnAe3xOcDKMz8KCn5lrLb/6Dla7Rp9LHKGkUfyI11EZt m+OIriwy9oDQquKyVuLQVGAxXKk+3pyxMqLB0i3hLYamT3vzoPctyVwjuRuKoU3E CbF0YhCoxvWMvjHsolwYzx00DbLXouE4BGKvPjnhw5hwtdoZ9Px0ZnCXCxVXi8z/ mlw7a2ptKEiHQVjuPPbttq+dA+ez7pbWonWVod5TMaPtyEZu5XfPD+0pMboceHZg H8ehgUhV3mzEJiisFGg1q9hj+4BaFl5m4tvqp43inCCdShE78CNnOPzJ7WCjKJqi jGvHjeMoVx3rZXHcZDAzfIZvDigp9uAfzjRJjpRG8sg5sDQVC7vdUhQDe5TorKT2 Vb0tdVYxoEpMJ3dhU6Ds5JxMR6GTLjsjTqOkAl6db3HxulwfEpr7YjOpfODR+ttA BeIcUcMLsDHayIaQaMLIftHxOkfX7UxoFW9CMG5UMQf/m3eEgVUwgK/E5sUJRUTo yhRzJ4NAP4fgc4YH9tbzvUrhfdCXCBEOn6IlDQL66SZr8Mm+Ggu4Ij4TnKWXLrXL nSTDDa42kPOvtedKqxC/uXE7rrfh+uyw6J6OjSl6u86TIebndLuDo5DTdWKh8rsg fvZZ6332dfMp8JC9/4YnYIJdI7acInSoyHp52OB+2+dgYCr5OrZFjjKS7nELVfo7 OxGy6uH3NHF9qyUEf3MN17TRHI7jP3zKbXcDTPSyxLQkWe/CU5B251CTmoTSidSW EhKnPlGZYbpVQJ4KGEL5UeY8W9PXQo4Dl7TmXBGvuPqNF8kMB3XrPIph7GmihmX0 nlJqLk9eiRFmUETS0IdAyKJrm4R9Hf6rjYCbXlaApylyVUdSZ2BxgeoTY9BA6Kgf 3xlgMv01MoUkXMx2+OLIc9MzhButQiDxh3mfS012CjKqUFrJhRSa8DOpUfVgmXpq /HP4drWamLWYJR8FsmJS11ZYc1EK/ctJTSpqfewvoUGOSHomhh7zXn1Acb6+9/3p bcrJjoR5K8Jg6NlG4dSNkpY/x92I7bFLXFqELIH5tteDrlQen5eASjaiyPPAoOw8 IGfOmFS4VUPh1VP6g8Jtn5Hr2qXB3DoQoI6EvUZhJ6GJfi67mx5VKux6G9MzJkix GU1cL4WzWK2DU0l39UxXjS+4TmOYbrqLVnVMjusX0fwb8LkDC/fVohbhLwhHNwu6 nSTSEpS9zSDrv1JXFtAtPv6XCSFs6ssPWJMwGSdThn7EfV0GEhG2mCzTyVhwxxQo 6U/Suqq4oMZoracPUCZx0E4u/bb4KBoFA/eBNPJENTR18IiV+D7wAxlxauO3N1t4 iJxwrrvSgQPmOGuxrh5LVD41UXYUWLtndzabnpByppFn2MbmvrqJgon0MSs84cTA 7scnbPu1V3PpKy/t67gtVw9Ue8hLjrskWB1JPFYr7vRWvJzYjfbflyroF+QEJ3TA 6rTfUC9+ePci6T+i9jF4xcmzqYzRtnGtp5nRUitJGw0uwBTDwzfI2WD6ltvvu7lc pHuzvY5zEapuu1JhjHLUd+OE8rVVM999DUXo/IDLsWyRCphCiYfVXJNogd9rB0Ta 5AhVgpRhxkarBURZyLTYj7NRxCsbHq7XExJNrIdRG/KlBQfyEyIzZ7E= -----END ENCRYPTED PRIVATE KEY----- EOF assert_same_rsa rsa, OpenSSL::PKey.read(pem, "abcdefgh") end def test_params key = Fixtures.pkey("rsa2048") assert_equal(2048, key.n.num_bits) assert_equal(key.n, key.params["n"]) assert_equal(65537, key.e) assert_equal(key.e, key.params["e"]) [:d, :p, :q, :dmp1, :dmq1, :iqmp].each do |name| assert_kind_of(OpenSSL::BN, key.send(name)) assert_equal(key.send(name), key.params[name.to_s]) end pubkey = OpenSSL::PKey.read(key.public_to_der) assert_equal(key.n, pubkey.n) assert_equal(key.e, pubkey.e) [:d, :p, :q, :dmp1, :dmq1, :iqmp].each do |name| assert_nil(pubkey.send(name)) assert_nil(pubkey.params[name.to_s]) end end def test_dup key = Fixtures.pkey("rsa-1") key2 = key.dup assert_equal key.params, key2.params # PKey is immutable in OpenSSL >= 3.0 if !openssl?(3, 0, 0) key2.set_key(key2.n, 3, key2.d) assert_not_equal key.params, key2.params end end def test_marshal key = Fixtures.pkey("rsa-1") deserialized = Marshal.load(Marshal.dump(key)) assert_equal key.to_der, deserialized.to_der end private def assert_same_rsa(expected, key) check_component(expected, key, [:n, :e, :d, :p, :q, :dmp1, :dmq1, :iqmp]) end end end ================================================ FILE: test/openssl/test_provider.rb ================================================ # frozen_string_literal: true require_relative 'utils' if defined?(OpenSSL) && defined?(OpenSSL::Provider) class OpenSSL::TestProvider < OpenSSL::TestCase def test_openssl_provider_name_inspect with_openssl <<-'end;' provider = OpenSSL::Provider.load("default") assert_equal("default", provider.name) assert_not_nil(provider.inspect) end; end def test_openssl_provider_names # We expect the following providers are loaded in the cases: # * Non-FIPS: default # * FIPS: fips, base # Use the null provider to test the added provider. # See provider(7) - OPENSSL PROVIDERS to see the list of providers, and # OSSL_PROVIDER-null(7) to check the details of the null provider. with_openssl <<-'end;' num = OpenSSL::Provider.provider_names.size added_provider = OpenSSL::Provider.load("null") assert_equal(num + 1, OpenSSL::Provider.provider_names.size) assert_includes(OpenSSL::Provider.provider_names, "null") assert_equal(true, added_provider.unload) assert_equal(num, OpenSSL::Provider.provider_names.size) assert_not_includes(OpenSSL::Provider.provider_names, "null") end; end def test_unloaded_openssl_provider with_openssl <<-'end;' default_provider = OpenSSL::Provider.load("default") assert_equal(true, default_provider.unload) assert_raise(OpenSSL::Provider::ProviderError) { default_provider.name } assert_raise(OpenSSL::Provider::ProviderError) { default_provider.unload } end; end def test_openssl_legacy_provider # The legacy provider is not supported on FIPS. omit_on_fips with_openssl(<<-'end;') begin OpenSSL::Provider.load("default") OpenSSL::Provider.load("legacy") rescue OpenSSL::Provider::ProviderError omit "Only for OpenSSL with legacy provider" end algo = "RC4" data = "a" * 1000 key = OpenSSL::Random.random_bytes(16) # default provider does not support RC4 cipher = OpenSSL::Cipher.new(algo) cipher.encrypt cipher.key = key encrypted = cipher.update(data) + cipher.final other_cipher = OpenSSL::Cipher.new(algo) other_cipher.decrypt other_cipher.key = key decrypted = other_cipher.update(encrypted) + other_cipher.final assert_equal(data, decrypted) end; end private # this is required because OpenSSL::Provider methods change global state def with_openssl(code, **opts) assert_separately(["-ropenssl"], <<~"end;", **opts) #{code} end; end end end ================================================ FILE: test/openssl/test_random.rb ================================================ # frozen_string_literal: true require_relative "utils" if defined?(OpenSSL) 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 end ================================================ FILE: test/openssl/test_ssl.rb ================================================ # frozen_string_literal: true require_relative "utils" if defined?(OpenSSL::SSL) class OpenSSL::TestSSL < OpenSSL::SSLTestCase def test_bad_socket bad_socket = Struct.new(:sync).new assert_raise TypeError do socket = OpenSSL::SSL::SSLSocket.new bad_socket # if the socket is not a T_FILE, `connect` will segv because it tries # to get the underlying file descriptor but the API it calls assumes # the object type is T_FILE socket.connect end end def test_ctx_setup ctx = OpenSSL::SSL::SSLContext.new assert_equal true, ctx.setup assert_predicate ctx, :frozen? assert_equal nil, ctx.setup end def test_ctx_options ctx = OpenSSL::SSL::SSLContext.new 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_ctx_options_config omit "LibreSSL and AWS-LC do not support OPENSSL_CONF" if libressl? || aws_lc? Tempfile.create("openssl.cnf") { |f| f.puts(<<~EOF) openssl_conf = default_conf [default_conf] ssl_conf = ssl_sect [ssl_sect] system_default = ssl_default_sect [ssl_default_sect] Options = -SessionTicket EOF f.close assert_separately([{ "OPENSSL_CONF" => f.path }, "-ropenssl"], <<~"end;") ctx = OpenSSL::SSL::SSLContext.new assert_equal OpenSSL::SSL::OP_NO_TICKET, ctx.options & OpenSSL::SSL::OP_NO_TICKET ctx.set_params assert_equal OpenSSL::SSL::OP_NO_TICKET, ctx.options & OpenSSL::SSL::OP_NO_TICKET end; } 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) { |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 ssl.puts "abc"; assert_equal "abc\n", ssl.gets ensure ssl&.close sock&.close end } end def test_socket_open start_server { |port| begin ssl = OpenSSL::SSL::SSLSocket.open("127.0.0.1", port) ssl.sync_close = true ssl.connect ssl.puts "abc"; assert_equal "abc\n", ssl.gets ensure ssl&.close end } end def test_socket_open_with_context start_server { |port| begin ctx = OpenSSL::SSL::SSLContext.new ssl = OpenSSL::SSL::SSLSocket.open("127.0.0.1", port, context: ctx) ssl.sync_close = true ssl.connect assert_equal ssl.context, ctx ssl.puts "abc"; assert_equal "abc\n", ssl.gets ensure ssl&.close end } end def test_socket_open_with_local_address_port_context start_server { |port| begin # Guess a free port number random_port = rand(49152..65535) ctx = OpenSSL::SSL::SSLContext.new ssl = OpenSSL::SSL::SSLSocket.open("127.0.0.1", port, "127.0.0.1", random_port, context: ctx) ssl.sync_close = true ssl.connect assert_equal ctx, ssl.context assert_equal random_port, ssl.io.local_address.ip_port ssl.puts "abc"; assert_equal "abc\n", ssl.gets rescue Errno::EADDRINUSE, Errno::EACCES ensure ssl&.close end } end def test_socket_close_write server_proc = proc do |ctx, ssl| message = ssl.read ssl.write(message) ssl.close_write ensure ssl.close end start_server(server_proc: server_proc) do |port| ctx = OpenSSL::SSL::SSLContext.new ssl = OpenSSL::SSL::SSLSocket.open("127.0.0.1", port, context: ctx) ssl.sync_close = true ssl.connect message = "abc"*1024 ssl.write message ssl.close_write assert_equal message, ssl.read ensure ssl&.close end end def test_add_certificate ctx_proc = -> ctx { # Unset values set by start_server ctx.cert = ctx.key = ctx.extra_chain_cert = nil ctx.add_certificate(@svr_cert, @svr_key, [@ca_cert]) # RSA } start_server(ctx_proc: ctx_proc) do |port| server_connect(port) { |ssl| assert_equal @svr_cert.subject, ssl.peer_cert.subject assert_equal [@svr_cert.subject, @ca_cert.subject], ssl.peer_cert_chain.map(&:subject) ssl.puts "abc"; assert_equal "abc\n", ssl.gets } end end def test_add_certificate_multiple_certs ca2_key = Fixtures.pkey("rsa-3") ca2_exts = [ ["basicConstraints", "CA:TRUE", true], ["keyUsage", "cRLSign, keyCertSign", true], ] ca2_dn = OpenSSL::X509::Name.parse_rfc2253("CN=CA2") ca2_cert = issue_cert(ca2_dn, ca2_key, 123, ca2_exts, nil, nil) ecdsa_key = Fixtures.pkey("p256") exts = [ ["keyUsage", "digitalSignature", false], ] ecdsa_dn = OpenSSL::X509::Name.parse_rfc2253("CN=localhost2") ecdsa_cert = issue_cert(ecdsa_dn, ecdsa_key, 456, exts, ca2_cert, ca2_key) ctx_proc = -> ctx { # Unset values set by start_server ctx.cert = ctx.key = ctx.extra_chain_cert = nil ctx.add_certificate(@svr_cert, @svr_key, [@ca_cert]) # RSA ctx.add_certificate(ecdsa_cert, ecdsa_key, [ca2_cert]) } start_server(ctx_proc: ctx_proc) do |port| ctx = OpenSSL::SSL::SSLContext.new ctx.max_version = :TLS1_2 # TODO: We need this to force certificate type ctx.ciphers = "aECDSA" server_connect(port, ctx) { |ssl| assert_equal ecdsa_cert.subject, ssl.peer_cert.subject assert_equal [ecdsa_cert.subject, ca2_cert.subject], ssl.peer_cert_chain.map(&:subject) } ctx = OpenSSL::SSL::SSLContext.new ctx.max_version = :TLS1_2 ctx.ciphers = "aRSA" server_connect(port, ctx) { |ssl| assert_equal @svr_cert.subject, ssl.peer_cert.subject assert_equal [@svr_cert.subject, @ca_cert.subject], ssl.peer_cert_chain.map(&:subject) } end end def test_extra_chain_cert_auto_chain start_server { |port| server_connect(port) { |ssl| ssl.puts "abc"; assert_equal "abc\n", ssl.gets assert_equal @svr_cert.to_der, ssl.peer_cert.to_der assert_equal [@svr_cert], ssl.peer_cert_chain } } # AWS-LC enables SSL_MODE_NO_AUTO_CHAIN by default unless aws_lc? ctx_proc = -> ctx { # Sanity check: start_server won't set extra_chain_cert assert_nil ctx.extra_chain_cert ctx.cert_store = OpenSSL::X509::Store.new.tap { |store| store.add_cert(@ca_cert) } } start_server(ctx_proc: ctx_proc) { |port| server_connect(port) { |ssl| ssl.puts "abc"; assert_equal "abc\n", ssl.gets assert_equal @svr_cert.to_der, ssl.peer_cert.to_der assert_equal [@svr_cert, @ca_cert], ssl.peer_cert_chain } } end end def test_sysread_and_syswrite start_server { |port| server_connect(port) { |ssl| str = +("x" * 100 + "\n") ssl.syswrite(str) newstr = ssl.sysread(str.bytesize) assert_equal(str, newstr) buf = String.new ssl.syswrite(str) assert_same buf, ssl.sysread(str.size, buf) assert_equal(str, buf) obj = Object.new obj.define_singleton_method(:to_str) { str } ssl.syswrite(obj) assert_equal(str, ssl.sysread(str.bytesize)) } } end def test_read_with_timeout omit "does not support timeout" unless IO.method_defined?(:timeout) start_server do |port| server_connect(port) do |ssl| str = +("x" * 100 + "\n") ssl.syswrite(str) assert_equal(str, ssl.sysread(str.bytesize)) ssl.timeout = 0.1 assert_raise(IO::TimeoutError) { ssl.sysread(1) } ssl.syswrite(str) assert_equal(str, ssl.sysread(str.bytesize)) buf = "orig".b assert_raise(IO::TimeoutError) { ssl.sysread(1, buf) } assert_equal("orig", buf) assert_nothing_raised { buf.clear } end end end def test_getbyte start_server { |port| server_connect(port) { |ssl| str = +("x" * 100 + "\n") ssl.syswrite(str) newstr = str.bytesize.times.map { |i| ssl.getbyte }.pack("C*") assert_equal(str, newstr) } } end def test_readbyte start_server { |port| server_connect(port) { |ssl| str = +("x" * 100 + "\n") ssl.syswrite(str) newstr = str.bytesize.times.map { |i| ssl.readbyte }.pack("C*") assert_equal(str, newstr) } } end def test_sync_close start_server do |port| begin sock = TCPSocket.new("127.0.0.1", port) ssl = OpenSSL::SSL::SSLSocket.new(sock) ssl.connect ssl.puts "abc"; assert_equal "abc\n", ssl.gets 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.puts "abc"; assert_equal "abc\n", ssl.gets ssl.close assert_predicate sock, :closed? ensure sock&.close end end end def test_sync_close_initialize_opt start_server do |port| begin sock = TCPSocket.new("127.0.0.1", port) ssl = OpenSSL::SSL::SSLSocket.new(sock, sync_close: true) assert_equal true, ssl.sync_close ssl.connect ssl.puts "abc"; assert_equal "abc\n", ssl.gets ssl.close assert_predicate sock, :closed? ensure sock&.close end end end def test_copy_stream start_server do |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_verify_mode_default ctx = OpenSSL::SSL::SSLContext.new assert_equal OpenSSL::SSL::VERIFY_NONE, ctx.verify_mode end def test_verify_mode_server_cert start_server(ignore_listener_error: true) { |port| populated_store = OpenSSL::X509::Store.new populated_store.add_cert(@ca_cert) empty_store = OpenSSL::X509::Store.new # Valid certificate, SSL_VERIFY_PEER ctx = OpenSSL::SSL::SSLContext.new ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER ctx.cert_store = populated_store assert_nothing_raised { server_connect(port, ctx) { |ssl| ssl.puts("abc"); ssl.gets } } # Invalid certificate, SSL_VERIFY_NONE ctx = OpenSSL::SSL::SSLContext.new ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE ctx.cert_store = empty_store assert_nothing_raised { server_connect(port, ctx) { |ssl| ssl.puts("abc"); ssl.gets } } # Invalid certificate, SSL_VERIFY_PEER ctx = OpenSSL::SSL::SSLContext.new ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER ctx.cert_store = empty_store assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx) } } end def test_verify_mode_client_cert_required # Optional, client certificate not supplied vflag = OpenSSL::SSL::VERIFY_PEER accept_proc = -> ssl { assert_equal nil, ssl.peer_cert } start_server(verify_mode: vflag, accept_proc: accept_proc) { |port| assert_nothing_raised { server_connect(port) { |ssl| ssl.puts("abc"); ssl.gets } } } # Required, client certificate not supplied vflag = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT start_server(verify_mode: vflag, ignore_listener_error: true) { |port| assert_handshake_error { server_connect(port) { |ssl| ssl.puts("abc"); ssl.gets } } } end def test_client_auth_success vflag = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT ctx_proc = proc { |ctx| store = OpenSSL::X509::Store.new store.add_cert(@ca_cert) store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT ctx.cert_store = store # LibreSSL doesn't support client_cert_cb in TLS 1.3 ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION if libressl? } start_server(verify_mode: vflag, ctx_proc: ctx_proc) { |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_cert_cb_ignore_error vflag = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT start_server(verify_mode: vflag, ignore_listener_error: true) do |port| ctx = OpenSSL::SSL::SSLContext.new ctx.client_cert_cb = -> ssl { raise "exception in client_cert_cb must be suppressed" } # 1. Exception in client_cert_cb is suppressed # 2. No client certificate will be sent to the server # 3. SSL_VERIFY_FAIL_IF_NO_PEER_CERT causes the handshake to fail assert_handshake_error { server_connect(port, ctx) { |ssl| ssl.puts("abc"); ssl.gets } } end end def test_client_ca pend "LibreSSL doesn't support certificate_authorities" if libressl? ctx_proc = Proc.new do |ctx| store = OpenSSL::X509::Store.new store.add_cert(@ca_cert) store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT ctx.cert_store = store 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) { |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) ssl.puts "abc"; assert_equal "abc\n", ssl.gets } } end def test_unstarted_session start_server do |port| sock = TCPSocket.new("127.0.0.1", port) ssl = OpenSSL::SSL::SSLSocket.new(sock) assert_raise(OpenSSL::SSL::SSLError) { ssl.syswrite("data") } assert_raise(OpenSSL::SSL::SSLError) { ssl.sysread(1) } ssl.connect ssl.puts "abc" assert_equal "abc\n", ssl.gets ensure ssl&.close sock&.close end end def test_parallel start_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) { |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_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, ssl.verify_result) ensure ssl.close end } start_server { |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 server_connect(port, ctx) { |ssl| assert_equal(OpenSSL::X509::V_OK, ssl.verify_result) ssl.puts "abc"; assert_equal "abc\n", ssl.gets } } start_server(ignore_listener_error: true) { |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) { |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 EnvUtil.suppress_warning 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_ca_file start_server(ignore_listener_error: true) { |port| # X509_STORE is shared; setting ca_file to SSLContext affects store store = OpenSSL::X509::Store.new assert_equal false, store.verify(@svr_cert) ctx = Tempfile.create("ca_cert.pem") { |f| f.puts(@ca_cert.to_pem) f.close ctx = OpenSSL::SSL::SSLContext.new ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER ctx.cert_store = store ctx.ca_file = f.path ctx.setup ctx } assert_nothing_raised { server_connect(port, ctx) { |ssl| ssl.puts("abc"); ssl.gets } } assert_equal true, store.verify(@svr_cert) } end def test_ca_file_not_found path = Tempfile.create("ca_cert.pem") { |f| f.path } ctx = OpenSSL::SSL::SSLContext.new ctx.ca_file = path # OpenSSL >= 1.1.0: /no certificate or crl found/ assert_raise(OpenSSL::SSL::SSLError) { ctx.setup } end def test_finished_messages server_finished = nil server_peer_finished = nil client_finished = nil client_peer_finished = nil start_server(accept_proc: proc { |server| server_finished = server.finished_message server_peer_finished = server.peer_finished_message }) { |port| ctx = OpenSSL::SSL::SSLContext.new ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE server_connect(port, ctx) { |ssl| ssl.puts "abc"; ssl.gets client_finished = ssl.finished_message client_peer_finished = ssl.peer_finished_message } } assert_not_nil(server_finished) assert_not_nil(client_finished) assert_equal(server_finished, client_peer_finished) assert_equal(server_peer_finished, client_finished) 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(?!-EDE|-CBC3))/ !~ v }, "weak ciphers are disabled" assert_equal 0, ctx.options & OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS assert_equal OpenSSL::SSL::OP_NO_COMPRESSION, ctx.options & OpenSSL::SSL::OP_NO_COMPRESSION end def test_post_connect_check_with_anon_ciphers # DH missing the q value on unknown named parameters is not FIPS-approved. omit_on_fips omit "AWS-LC does not support DHE ciphersuites" if aws_lc? ctx_proc = -> ctx { ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION ctx.ciphers = "aNULL" ctx.tmp_dh = Fixtures.pkey("dh-1") ctx.security_level = 0 } start_server(ctx_proc: ctx_proc) { |port| ctx = OpenSSL::SSL::SSLContext.new ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION 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 { |port| server_connect(port) { |ssl| ssl.puts "abc"; assert_equal "abc\n", ssl.gets 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,IP:127.0.0.1",false], ] @svr_cert = issue_cert(@svr, @svr_key, 4, exts, @ca_cert, @ca_key) start_server { |port| server_connect(port) { |ssl| ssl.puts "abc"; assert_equal "abc\n", ssl.gets 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 { |port| server_connect(port) { |ssl| ssl.puts "abc"; assert_equal "abc\n", ssl.gets 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(true, OpenSSL::SSL.verify_certificate_identity(cert, '13::17')) assert_equal(false, OpenSSL::SSL.verify_certificate_identity(cert, '13::18')) assert_equal(true, OpenSSL::SSL.verify_certificate_identity(cert, '13:0:0:0:0:0:0:17')) assert_equal(false, OpenSSL::SSL.verify_certificate_identity(cert, '44:0:0:0:0:0:0:17')) assert_equal(true, OpenSSL::SSL.verify_certificate_identity(cert, '0013:0000:0000:0000:0000:0000:0000:0017')) assert_equal(false, OpenSSL::SSL.verify_certificate_identity(cert, '1313:0000:0000:0000:0000:0000:0000:0017')) 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 https://www.rfc-editor.org/rfc/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) cert = OpenSSL::X509::Certificate.new cert.subject = OpenSSL::X509::Name.parse("/DC=some/DC=site/CN=Some Site") v = OpenSSL::ASN1::Sequence(san.split(",").map { |item| type, value = item.split(":", 2) case type when "DNS" then OpenSSL::ASN1::IA5String(value, 2, :IMPLICIT) when "IP" then OpenSSL::ASN1::OctetString(IPAddr.new(value).hton, 7, :IMPLICIT) else raise "unsupported" end }) cert.add_extension(OpenSSL::X509::Extension.new("subjectAltName", v)) 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_keylog_cb omit "Keylog callback is not supported" if libressl? prefix = 'CLIENT_RANDOM' context = OpenSSL::SSL::SSLContext.new context.min_version = context.max_version = OpenSSL::SSL::TLS1_2_VERSION cb_called = false context.keylog_cb = proc do |_sock, line| cb_called = true assert_equal(prefix, line.split.first) end start_server do |port| server_connect(port, context) do |ssl| ssl.puts "abc" assert_equal("abc\n", ssl.gets) assert_equal(true, cb_called) end end prefixes = [ 'SERVER_HANDSHAKE_TRAFFIC_SECRET', 'EXPORTER_SECRET', 'SERVER_TRAFFIC_SECRET_0', 'CLIENT_HANDSHAKE_TRAFFIC_SECRET', 'CLIENT_TRAFFIC_SECRET_0', ] context = OpenSSL::SSL::SSLContext.new context.min_version = context.max_version = OpenSSL::SSL::TLS1_3_VERSION cb_called = false context.keylog_cb = proc do |_sock, line| cb_called = true assert_not_nil(prefixes.delete(line.split.first)) end start_server do |port| server_connect(port, context) do |ssl| ssl.puts "abc" assert_equal("abc\n", ssl.gets) assert_equal(true, cb_called) end assert_equal(0, prefixes.size) end end def test_tlsext_hostname fooctx = OpenSSL::SSL::SSLContext.new fooctx.cert = @cli_cert fooctx.key = @cli_key ctx_proc = proc { |ctx| ctx.servername_cb = proc { |ssl, servername| case servername when "foo.example.com" fooctx when "bar.example.com" nil else raise "unreachable" end } } start_server(ctx_proc: ctx_proc) do |port| sock = TCPSocket.new("127.0.0.1", port) begin ssl = OpenSSL::SSL::SSLSocket.new(sock) ssl.hostname = "foo.example.com" ssl.connect assert_equal @cli_cert.serial, ssl.peer_cert.serial assert_predicate fooctx, :frozen? ssl.puts "abc"; assert_equal "abc\n", ssl.gets ensure ssl&.close sock.close end sock = TCPSocket.new("127.0.0.1", port) begin ssl = OpenSSL::SSL::SSLSocket.new(sock) ssl.hostname = "bar.example.com" ssl.connect assert_equal @svr_cert.serial, ssl.peer_cert.serial ssl.puts "abc"; assert_equal "abc\n", ssl.gets ensure ssl&.close sock.close end end end def test_servername_cb_exception sock1, sock2 = socketpair t = Thread.new { s1 = OpenSSL::SSL::SSLSocket.new(sock1) s1.hostname = "localhost" assert_raise_with_message(OpenSSL::SSL::SSLError, /unrecognized.name/i) { s1.connect } } ctx2 = OpenSSL::SSL::SSLContext.new ctx2.servername_cb = lambda { |args| raise RuntimeError, "foo" } s2 = OpenSSL::SSL::SSLSocket.new(sock2, ctx2) assert_raise_with_message(RuntimeError, "foo") { s2.accept } assert t.join ensure sock1.close sock2.close t.kill.join end def test_servername_cb_raises_an_exception_on_unknown_objects sock1, sock2 = socketpair t = Thread.new { s1 = OpenSSL::SSL::SSLSocket.new(sock1) s1.hostname = "localhost" assert_raise(OpenSSL::SSL::SSLError) { s1.connect } } ctx2 = OpenSSL::SSL::SSLContext.new ctx2.servername_cb = lambda { |args| Object.new } s2 = OpenSSL::SSL::SSLSocket.new(sock2, ctx2) assert_raise(ArgumentError) { s2.accept } assert t.join ensure sock1.close sock2.close t.kill.join end def test_accept_errors_include_peeraddr context = OpenSSL::SSL::SSLContext.new context.cert = @svr_cert context.key = @svr_key server = TCPServer.new("127.0.0.1", 0) port = server.connect_address.ip_port ssl_server = OpenSSL::SSL::SSLServer.new(server, context) t = Thread.new do assert_raise_with_message(OpenSSL::SSL::SSLError, /peeraddr=127\.0\.0\.1/) do ssl_server.accept end end sock = TCPSocket.new("127.0.0.1", port) sock << "\x00" * 1024 assert t.join ensure sock&.close server.close 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 |port| ctx = OpenSSL::SSL::SSLContext.new assert_equal false, ctx.verify_hostname 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 ssl.connect ssl.puts "abc"; assert_equal "abc\n", ssl.gets else assert_raise(OpenSSL::SSL::SSLError) { ssl.connect } end ensure ssl.close if ssl sock.close if sock end end end end def test_verify_hostname_failure_error_code ctx_proc = proc { |ctx| exts = [ ["keyUsage", "keyEncipherment,digitalSignature", true], ["subjectAltName", "DNS:a.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 |port| verify_callback_ok = verify_callback_err = nil 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 ctx.verify_callback = -> (preverify_ok, store_ctx) { verify_callback_ok = preverify_ok verify_callback_err = store_ctx.error preverify_ok } begin sock = TCPSocket.new("127.0.0.1", port) ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) ssl.hostname = "b.example.com" assert_raise(OpenSSL::SSL::SSLError) { ssl.connect } assert_equal false, verify_callback_ok assert_equal OpenSSL::X509::V_ERR_HOSTNAME_MISMATCH, verify_callback_err ensure sock&.close end end end def test_connect_certificate_verify_failed_exception_message start_server(ignore_listener_error: true) { |port| ctx = OpenSSL::SSL::SSLContext.new ctx.set_params assert_raise_with_message(OpenSSL::SSL::SSLError, /unable to get local issuer certificate/) { server_connect(port, ctx) } } ctx_proc = proc { |ctx| now = Time.now ctx.cert = issue_cert(@svr, @svr_key, 30, [], @ca_cert, @ca_key, not_before: now - 7200, not_after: now - 3600) } start_server(ignore_listener_error: true, ctx_proc: ctx_proc) { |port| store = OpenSSL::X509::Store.new store.add_cert(@ca_cert) ctx = OpenSSL::SSL::SSLContext.new ctx.set_params(cert_store: store) assert_raise_with_message(OpenSSL::SSL::SSLError, /expired/) { server_connect(port, ctx) } } 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) { |port| server_connect(port) { |ssl| ssl.puts('hello') assert_equal("hello\n", ssl.gets) } } end def check_supported_protocol_versions possible_versions = [ OpenSSL::SSL::SSL3_VERSION, OpenSSL::SSL::TLS1_VERSION, OpenSSL::SSL::TLS1_1_VERSION, OpenSSL::SSL::TLS1_2_VERSION, OpenSSL::SSL::TLS1_3_VERSION, ] supported = [] ctx_proc = proc { |ctx| # The default security level is 1 in OpenSSL <= 3.1, 2 in OpenSSL >= 3.2 # In OpenSSL >= 3.0, TLS 1.1 or older is disabled at level 1 ctx.security_level = 0 # Explicitly reset them to avoid influenced by OPENSSL_CONF ctx.min_version = ctx.max_version = nil } start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port| possible_versions.each do |ver| ctx = OpenSSL::SSL::SSLContext.new ctx.security_level = 0 ctx.min_version = ctx.max_version = ver server_connect(port, ctx) { |ssl| ssl.puts "abc"; assert_equal "abc\n", ssl.gets } supported << ver rescue OpenSSL::SSL::SSLError, Errno::ECONNRESET end end # Sanity check: in our test suite we assume these are always supported assert_include(supported, OpenSSL::SSL::TLS1_2_VERSION) assert_include(supported, OpenSSL::SSL::TLS1_3_VERSION) supported end def test_set_params_min_version supported = check_supported_protocol_versions store = OpenSSL::X509::Store.new store.add_cert(@ca_cert) if supported.include?(OpenSSL::SSL::SSL3_VERSION) # SSLContext#set_params properly disables SSL 3.0 by default ctx_proc = proc { |ctx| ctx.min_version = ctx.max_version = OpenSSL::SSL::SSL3_VERSION } start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port| ctx = OpenSSL::SSL::SSLContext.new ctx.set_params(cert_store: store, verify_hostname: false) assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx) } } end end def test_minmax_version supported = check_supported_protocol_versions # name: The string that would be returned by SSL_get_version() # method: The version-specific method name (if any) vmap = { OpenSSL::SSL::SSL3_VERSION => { name: "SSLv3", method: "SSLv3" }, OpenSSL::SSL::SSL3_VERSION => { name: "SSLv3", method: "SSLv3" }, OpenSSL::SSL::TLS1_VERSION => { name: "TLSv1", method: "TLSv1" }, OpenSSL::SSL::TLS1_1_VERSION => { name: "TLSv1.1", method: "TLSv1_1" }, OpenSSL::SSL::TLS1_2_VERSION => { name: "TLSv1.2", method: "TLSv1_2" }, OpenSSL::SSL::TLS1_3_VERSION => { name: "TLSv1.3", method: nil }, } # Server enables a single version supported.each do |ver| ctx_proc = proc { |ctx| ctx.security_level = 0 ctx.min_version = ctx.max_version = ver } start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port| supported.each do |cver| # Client enables a single version ctx1 = OpenSSL::SSL::SSLContext.new ctx1.security_level = 0 ctx1.min_version = ctx1.max_version = cver if ver == cver server_connect(port, ctx1) { |ssl| assert_equal vmap[cver][:name], ssl.ssl_version ssl.puts "abc"; assert_equal "abc\n", ssl.gets } else assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx1) } end # There is no version-specific SSL methods for TLS 1.3 if cver <= OpenSSL::SSL::TLS1_2_VERSION # Client enables a single version using #ssl_version= ctx2 = OpenSSL::SSL::SSLContext.new ctx2.security_level = 0 ctx2.ssl_version = vmap[cver][:method] if ver == cver server_connect(port, ctx2) { |ssl| assert_equal vmap[cver][:name], ssl.ssl_version ssl.puts "abc"; assert_equal "abc\n", ssl.gets } else assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx2) } end end end # Client enables all supported versions ctx3 = OpenSSL::SSL::SSLContext.new ctx3.security_level = 0 ctx3.min_version = ctx3.max_version = nil server_connect(port, ctx3) { |ssl| assert_equal vmap[ver][:name], ssl.ssl_version ssl.puts "abc"; assert_equal "abc\n", ssl.gets } } end if supported.size == 1 pend "More than one protocol version must be supported" end # Server sets min_version (earliest is disabled) sver = supported[1] ctx_proc = proc { |ctx| ctx.security_level = 0 ctx.min_version = sver } start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port| supported.each do |cver| # Client sets min_version ctx1 = OpenSSL::SSL::SSLContext.new ctx1.security_level = 0 ctx1.min_version = cver ctx1.max_version = 0 server_connect(port, ctx1) { |ssl| assert_equal vmap[supported.last][:name], ssl.ssl_version ssl.puts "abc"; assert_equal "abc\n", ssl.gets } # Client sets max_version ctx2 = OpenSSL::SSL::SSLContext.new ctx2.security_level = 0 ctx2.min_version = 0 ctx2.max_version = cver if cver >= sver server_connect(port, ctx2) { |ssl| assert_equal vmap[cver][:name], ssl.ssl_version ssl.puts "abc"; assert_equal "abc\n", ssl.gets } else assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx2) } end end } # Server sets max_version (latest is disabled) sver = supported[-2] ctx_proc = proc { |ctx| ctx.security_level = 0 ctx.min_version = 0 ctx.max_version = sver } start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port| supported.each do |cver| # Client sets min_version ctx1 = OpenSSL::SSL::SSLContext.new ctx1.min_version = cver if cver <= sver server_connect(port, ctx1) { |ssl| assert_equal vmap[sver][:name], ssl.ssl_version ssl.puts "abc"; assert_equal "abc\n", ssl.gets } else assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx1) } end # Client sets max_version ctx2 = OpenSSL::SSL::SSLContext.new ctx2.security_level = 0 ctx2.min_version = 0 ctx2.max_version = cver server_connect(port, ctx2) { |ssl| if cver >= sver assert_equal vmap[sver][:name], ssl.ssl_version else assert_equal vmap[cver][:name], ssl.ssl_version end ssl.puts "abc"; assert_equal "abc\n", ssl.gets } end } end def test_minmax_version_system_default omit "LibreSSL and AWS-LC do not support OPENSSL_CONF" if libressl? || aws_lc? Tempfile.create("openssl.cnf") { |f| f.puts(<<~EOF) openssl_conf = default_conf [default_conf] ssl_conf = ssl_sect [ssl_sect] system_default = ssl_default_sect [ssl_default_sect] MaxProtocol = TLSv1.2 EOF f.close start_server(ignore_listener_error: true) do |port| assert_separately([{ "OPENSSL_CONF" => f.path }, "-ropenssl", "-", port.to_s], <<~"end;") sock = TCPSocket.new("127.0.0.1", ARGV[0].to_i) ctx = OpenSSL::SSL::SSLContext.new ctx.min_version = OpenSSL::SSL::TLS1_2_VERSION ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) ssl.sync_close = true ssl.connect assert_equal("TLSv1.2", ssl.ssl_version) ssl.puts("abc"); assert_equal("abc\n", ssl.gets) ssl.close end; assert_separately([{ "OPENSSL_CONF" => f.path }, "-ropenssl", "-", port.to_s], <<~"end;") sock = TCPSocket.new("127.0.0.1", ARGV[0].to_i) ctx = OpenSSL::SSL::SSLContext.new ctx.min_version = OpenSSL::SSL::TLS1_2_VERSION ctx.max_version = nil ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) ssl.sync_close = true ssl.connect assert_equal("TLSv1.3", ssl.ssl_version) ssl.puts("abc"); assert_equal("abc\n", ssl.gets) ssl.close end; end } end def test_respect_system_default_min omit "LibreSSL and AWS-LC do not support OPENSSL_CONF" if libressl? || aws_lc? Tempfile.create("openssl.cnf") { |f| f.puts(<<~EOF) openssl_conf = default_conf [default_conf] ssl_conf = ssl_sect [ssl_sect] system_default = ssl_default_sect [ssl_default_sect] MinProtocol = TLSv1.3 EOF f.close ctx_proc = proc { |ctx| ctx.min_version = ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION } start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port| assert_separately([{ "OPENSSL_CONF" => f.path }, "-ropenssl", "-", port.to_s], <<~"end;") sock = TCPSocket.new("127.0.0.1", ARGV[0].to_i) ctx = OpenSSL::SSL::SSLContext.new ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) ssl.sync_close = true assert_raise(OpenSSL::SSL::SSLError) do ssl.connect end ssl.close end; end ctx_proc = proc { |ctx| ctx.min_version = ctx.max_version = OpenSSL::SSL::TLS1_3_VERSION } start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port| assert_separately([{ "OPENSSL_CONF" => f.path }, "-ropenssl", "-", port.to_s], <<~"end;") sock = TCPSocket.new("127.0.0.1", ARGV[0].to_i) ctx = OpenSSL::SSL::SSLContext.new ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) ssl.sync_close = true ssl.connect assert_equal("TLSv1.3", ssl.ssl_version) ssl.puts("abc"); assert_equal("abc\n", ssl.gets) ssl.close end; end } end def test_options_disable_versions # It's recommended to use SSLContext#{min,max}_version= instead in real # applications. The purpose of this test case is to check that SSL options # are properly propagated to OpenSSL library. supported = check_supported_protocol_versions if !supported.include?(OpenSSL::SSL::TLS1_2_VERSION) || !supported.include?(OpenSSL::SSL::TLS1_3_VERSION) pend "this test case requires both TLS 1.2 and TLS 1.3 to be supported " \ "and enabled by default" end # Server disables TLS 1.2 and earlier ctx_proc = proc { |ctx| ctx.options |= OpenSSL::SSL::OP_NO_SSLv2 | OpenSSL::SSL::OP_NO_SSLv3 | OpenSSL::SSL::OP_NO_TLSv1 | OpenSSL::SSL::OP_NO_TLSv1_1 | OpenSSL::SSL::OP_NO_TLSv1_2 } start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port| # Client only supports TLS 1.2 ctx1 = OpenSSL::SSL::SSLContext.new ctx1.min_version = ctx1.max_version = OpenSSL::SSL::TLS1_2_VERSION assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx1) } # Client only supports TLS 1.3 ctx2 = OpenSSL::SSL::SSLContext.new ctx2.min_version = ctx2.max_version = OpenSSL::SSL::TLS1_3_VERSION assert_nothing_raised { server_connect(port, ctx2) { } } } # Server only supports TLS 1.2 ctx_proc = proc { |ctx| ctx.min_version = ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION } start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port| # Client doesn't support TLS 1.2 ctx1 = OpenSSL::SSL::SSLContext.new ctx1.options |= OpenSSL::SSL::OP_NO_TLSv1_2 assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx1) } # Client supports TLS 1.2 by default ctx2 = OpenSSL::SSL::SSLContext.new ctx2.options |= OpenSSL::SSL::OP_NO_TLSv1_3 assert_nothing_raised { server_connect(port, ctx2) { } } } end def test_ssl_methods_constant EnvUtil.suppress_warning { # Deprecated in v2.1.0 base = [:TLSv1_2, :TLSv1_1, :TLSv1, :SSLv3, :SSLv2, :SSLv23] base.each do |name| assert_include OpenSSL::SSL::SSLContext::METHODS, name assert_include OpenSSL::SSL::SSLContext::METHODS, :"#{name}_client" assert_include OpenSSL::SSL::SSLContext::METHODS, :"#{name}_server" end } 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(ctx_proc: ctx_proc) { |port| server_connect(port) { |ssl| assert_equal(1, num_handshakes) ssl.puts "abc"; assert_equal "abc\n", ssl.gets } } end 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(ctx_proc: ctx_proc) { |port| ctx = OpenSSL::SSL::SSLContext.new ctx.alpn_protocols = advertised server_connect(port, ctx) { |ssl| assert_equal(advertised.first, ssl.alpn_protocol) ssl.puts "abc"; assert_equal "abc\n", ssl.gets } } end def test_alpn_protocol_selection_cancel sock1, sock2 = socketpair ctx1 = OpenSSL::SSL::SSLContext.new ctx1.cert = @svr_cert ctx1.key = @svr_key ctx1.alpn_select_cb = -> (protocols) { nil } ssl1 = OpenSSL::SSL::SSLSocket.new(sock1, ctx1) ctx2 = OpenSSL::SSL::SSLContext.new 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 def test_npn_protocol_selection_ary return unless OpenSSL::SSL::SSLContext.method_defined?(:npn_select_cb) advertised = ["http/1.1", "spdy/2"] ctx_proc = proc { |ctx| ctx.npn_protocols = advertised } start_server(ctx_proc: ctx_proc) { |port| selector = lambda { |which| ctx = OpenSSL::SSL::SSLContext.new ctx.max_version = :TLS1_2 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 return unless OpenSSL::SSL::SSLContext.method_defined?(:npn_select_cb) 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(ctx_proc: ctx_proc) { |port| selector = lambda { |selected, which| ctx = OpenSSL::SSL::SSLContext.new ctx.max_version = :TLS1_2 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 return unless OpenSSL::SSL::SSLContext.method_defined?(:npn_select_cb) ctx_proc = Proc.new { |ctx| ctx.npn_protocols = ["http/1.1"] } start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port| ctx = OpenSSL::SSL::SSLContext.new ctx.max_version = :TLS1_2 ctx.npn_select_cb = -> (protocols) { raise RuntimeError.new } assert_raise(RuntimeError) { server_connect(port, ctx) } } end def test_npn_advertised_protocol_too_long return unless OpenSSL::SSL::SSLContext.method_defined?(:npn_select_cb) ctx = OpenSSL::SSL::SSLContext.new assert_raise(OpenSSL::SSL::SSLError) do ctx.npn_protocols = ["a" * 256] ctx.setup end end def test_npn_selected_protocol_too_long return unless OpenSSL::SSL::SSLContext.method_defined?(:npn_select_cb) ctx_proc = Proc.new { |ctx| ctx.npn_protocols = ["http/1.1"] } start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port| ctx = OpenSSL::SSL::SSLContext.new ctx.max_version = :TLS1_2 ctx.npn_select_cb = -> (protocols) { "a" * 256 } assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx) } } end def readwrite_loop_safe(ctx, ssl) readwrite_loop(ctx, ssl) rescue OpenSSL::SSL::SSLError end def test_close_after_socket_close start_server(server_proc: method(:readwrite_loop_safe)) { |port| sock = TCPSocket.new("127.0.0.1", port) ssl = OpenSSL::SSL::SSLSocket.new(sock) ssl.connect ssl.puts "abc"; assert_equal "abc\n", ssl.gets 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_get_ephemeral_key # kRSA is not FIPS-approved. omit_on_fips # kRSA ctx_proc1 = proc { |ctx| ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION ctx.ciphers = "kRSA" } start_server(ctx_proc: ctx_proc1, ignore_listener_error: true) do |port| ctx = OpenSSL::SSL::SSLContext.new ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION ctx.ciphers = "kRSA" begin server_connect(port, ctx) { |ssl| assert_nil ssl.tmp_key } rescue OpenSSL::SSL::SSLError # kRSA seems disabled raise unless $!.message =~ /no cipher/ end end # DHE # OpenSSL 3.0 added support for named FFDHE groups in TLS 1.3 # LibreSSL does not support named FFDHE groups currently # AWS-LC does not support DHE ciphersuites if openssl?(3, 0, 0) start_server do |port| ctx = OpenSSL::SSL::SSLContext.new ctx.groups = "ffdhe3072" server_connect(port, ctx) { |ssl| assert_instance_of OpenSSL::PKey::DH, ssl.tmp_key assert_equal 3072, ssl.tmp_key.p.num_bits ssl.puts "abc"; assert_equal "abc\n", ssl.gets } end end # ECDHE ctx_proc3 = proc { |ctx| ctx.groups = "P-256" } start_server(ctx_proc: ctx_proc3) do |port| server_connect(port) { |ssl| assert_instance_of OpenSSL::PKey::EC, ssl.tmp_key ssl.puts "abc"; assert_equal "abc\n", ssl.gets } end end def test_fallback_scsv supported = check_supported_protocol_versions unless supported.include?(OpenSSL::SSL::TLS1_1_VERSION) omit "TLS 1.1 support is required to run this test case" end omit "Fallback SCSV is not supported" if libressl? start_server do |port| ctx = OpenSSL::SSL::SSLContext.new ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION # Here is OK # TLS1.2 supported and this is what we ask the first time server_connect(port, ctx) end ctx_proc = proc { |ctx| ctx.security_level = 0 ctx.min_version = 0 ctx.max_version = OpenSSL::SSL::TLS1_1_VERSION } start_server(ctx_proc: ctx_proc) do |port| ctx = OpenSSL::SSL::SSLContext.new ctx.enable_fallback_scsv ctx.security_level = 0 ctx.min_version = 0 ctx.max_version = OpenSSL::SSL::TLS1_1_VERSION # Here is OK too # TLS1.2 not supported, fallback to TLS1.1 and signaling the fallback # Server doesn't support better, so connection OK server_connect(port, ctx) end # Here is not OK # TLS1.2 is supported, fallback to TLS1.1 (downgrade attack) and signaling the fallback # Server support better, so refuse the connection sock1, sock2 = socketpair begin # This test is for the downgrade protection mechanism of TLS1.2. # This is why ctx1 bounds max_version == TLS1.2. # Otherwise, this test fails when using openssl 1.1.1 (or later) that supports TLS1.3. # TODO: We may need another test for TLS1.3 because it seems to have a different mechanism. ctx1 = OpenSSL::SSL::SSLContext.new ctx1.security_level = 0 ctx1.min_version = 0 ctx1.max_version = OpenSSL::SSL::TLS1_2_VERSION s1 = OpenSSL::SSL::SSLSocket.new(sock1, ctx1) ctx2 = OpenSSL::SSL::SSLContext.new ctx2.enable_fallback_scsv ctx2.security_level = 0 ctx2.min_version = 0 ctx2.max_version = OpenSSL::SSL::TLS1_1_VERSION s2 = OpenSSL::SSL::SSLSocket.new(sock2, ctx2) # AWS-LC has slightly different error messages in all-caps. t = Thread.new { assert_raise_with_message(OpenSSL::SSL::SSLError, /inappropriate fallback|INAPPROPRIATE_FALLBACK/) { s2.connect } } assert_raise_with_message(OpenSSL::SSL::SSLError, /inappropriate fallback|INAPPROPRIATE_FALLBACK/) { s1.accept } t.join ensure sock1.close sock2.close end end def test_tmp_dh_callback # DH missing the q value on unknown named parameters is not FIPS-approved. omit_on_fips omit "AWS-LC does not support DHE ciphersuites" if aws_lc? dh = Fixtures.pkey("dh-1") called = false ctx_proc = -> ctx { ctx.max_version = :TLS1_2 ctx.ciphers = "DH:!NULL" ctx.tmp_dh_callback = ->(*args) { called = true dh } } start_server(ctx_proc: ctx_proc) do |port| ctx = OpenSSL::SSL::SSLContext.new ctx.groups = "P-256" # Exclude RFC 7919 groups server_connect(port, ctx) { |ssl| assert called, "dh callback should be called" assert_equal dh.to_der, ssl.tmp_key.to_der } end end def test_ciphersuites_method_tls_connection csuite = ['TLS_AES_128_GCM_SHA256', 'TLSv1.3', 128, 128] inputs = [csuite[0], [csuite[0]], [csuite]] start_server do |port| inputs.each do |input| cli_ctx = OpenSSL::SSL::SSLContext.new cli_ctx.min_version = cli_ctx.max_version = OpenSSL::SSL::TLS1_3_VERSION cli_ctx.ciphersuites = input server_connect(port, cli_ctx) do |ssl| assert_equal('TLSv1.3', ssl.ssl_version) assert_equal(csuite[0], ssl.cipher[0]) ssl.puts('abc'); assert_equal("abc\n", ssl.gets) end end end end def test_ciphersuites_method_nil_argument ssl_ctx = OpenSSL::SSL::SSLContext.new assert_nothing_raised { ssl_ctx.ciphersuites = nil } end def test_ciphersuites_method_frozen_object ssl_ctx = OpenSSL::SSL::SSLContext.new ssl_ctx.freeze assert_raise(FrozenError) { ssl_ctx.ciphersuites = 'TLS_AES_256_GCM_SHA384' } end def test_ciphersuites_method_bogus_csuite ssl_ctx = OpenSSL::SSL::SSLContext.new # AWS-LC has slightly different error messages in all-caps. assert_raise_with_message( OpenSSL::SSL::SSLError, /SSL_CTX_set_ciphersuites: (no cipher match|NO_CIPHER_MATCH)/i ) { ssl_ctx.ciphersuites = 'BOGUS' } end def test_ciphers_method_tls_connection csuite = ['ECDHE-RSA-AES256-GCM-SHA384', 'TLSv1.2', 256, 256] inputs = [csuite[0], [csuite[0]], [csuite]] start_server do |port| inputs.each do |input| cli_ctx = OpenSSL::SSL::SSLContext.new cli_ctx.min_version = cli_ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION cli_ctx.ciphers = input server_connect(port, cli_ctx) do |ssl| assert_equal('TLSv1.2', ssl.ssl_version) assert_equal(csuite[0], ssl.cipher[0]) ssl.puts('abc'); assert_equal("abc\n", ssl.gets) end end end end def test_ciphers_method_nil_argument ssl_ctx = OpenSSL::SSL::SSLContext.new assert_nothing_raised { ssl_ctx.ciphers = nil } end def test_ciphers_method_frozen_object ssl_ctx = OpenSSL::SSL::SSLContext.new ssl_ctx.freeze assert_raise(FrozenError) { ssl_ctx.ciphers = 'ECDHE-RSA-AES128-SHA' } end def test_ciphers_method_bogus_csuite ssl_ctx = OpenSSL::SSL::SSLContext.new # AWS-LC has slightly different error messages in all-caps. assert_raise_with_message( OpenSSL::SSL::SSLError, /SSL_CTX_set_cipher_list: (no cipher match|NO_CIPHER_MATCH)/i ) { ssl_ctx.ciphers = 'BOGUS' } end def test_sigalgs omit "SSL_CTX_set1_sigalgs_list() not supported" if libressl? svr_exts = [ ["keyUsage", "keyEncipherment,digitalSignature", true], ["subjectAltName", "DNS:localhost", false], ] ecdsa_key = Fixtures.pkey("p256") ecdsa_cert = issue_cert(@svr, ecdsa_key, 10, svr_exts, @ca_cert, @ca_key) ctx_proc = -> ctx { # Unset values set by start_server ctx.cert = ctx.key = ctx.extra_chain_cert = nil ctx.add_certificate(@svr_cert, @svr_key, [@ca_cert]) # RSA ctx.add_certificate(ecdsa_cert, ecdsa_key, [@ca_cert]) # ECDSA } start_server(ctx_proc: ctx_proc) do |port| ctx1 = OpenSSL::SSL::SSLContext.new ctx1.sigalgs = "rsa_pss_rsae_sha256" server_connect(port, ctx1) { |ssl| assert_kind_of(OpenSSL::PKey::RSA, ssl.peer_cert.public_key) ssl.puts("abc"); ssl.gets } ctx2 = OpenSSL::SSL::SSLContext.new ctx2.sigalgs = "ed25519:ecdsa_secp256r1_sha256" server_connect(port, ctx2) { |ssl| assert_kind_of(OpenSSL::PKey::EC, ssl.peer_cert.public_key) ssl.puts("abc"); ssl.gets } end # Frozen ssl_ctx = OpenSSL::SSL::SSLContext.new ssl_ctx.freeze assert_raise(FrozenError) { ssl_ctx.sigalgs = "ECDSA+SHA256:RSA+SHA256" } # Bogus ssl_ctx = OpenSSL::SSL::SSLContext.new assert_raise(TypeError) { ssl_ctx.sigalgs = nil } assert_raise(OpenSSL::SSL::SSLError) { ssl_ctx.sigalgs = "BOGUS" } end def test_client_sigalgs omit "SSL_CTX_set1_client_sigalgs_list() not supported" if libressl? || aws_lc? cli_exts = [ ["keyUsage", "keyEncipherment,digitalSignature", true], ["subjectAltName", "DNS:localhost", false], ] ecdsa_key = Fixtures.pkey("p256") ecdsa_cert = issue_cert(@cli, ecdsa_key, 10, cli_exts, @ca_cert, @ca_key) ctx_proc = -> ctx { store = OpenSSL::X509::Store.new store.add_cert(@ca_cert) store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT ctx.cert_store = store ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT ctx.client_sigalgs = "ECDSA+SHA256" } start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port| ctx1 = OpenSSL::SSL::SSLContext.new ctx1.add_certificate(@cli_cert, @cli_key) # RSA assert_handshake_error { server_connect(port, ctx1) { |ssl| ssl.puts("abc"); ssl.gets } } ctx2 = OpenSSL::SSL::SSLContext.new ctx2.add_certificate(ecdsa_cert, ecdsa_key) # ECDSA server_connect(port, ctx2) { |ssl| ssl.puts("abc"); ssl.gets } end end def test_get_sigalg # SSL_get0_signature_name() not supported # SSL_get0_peer_signature_name() not supported return unless openssl?(3, 5, 0) server_proc = -> (ctx, ssl) { assert_equal('rsa_pss_rsae_sha256', ssl.sigalg) assert_nil(ssl.peer_sigalg) readwrite_loop(ctx, ssl) } start_server(server_proc: server_proc) do |port| cli_ctx = OpenSSL::SSL::SSLContext.new server_connect(port, cli_ctx) do |ssl| assert_nil(ssl.sigalg) assert_equal('rsa_pss_rsae_sha256', ssl.peer_sigalg) ssl.puts "abc"; ssl.gets end end end def test_pqc_sigalg # PQC algorithm ML-DSA (FIPS 204) is supported on OpenSSL 3.5 or later. return unless openssl?(3, 5, 0) mldsa = Fixtures.pkey("mldsa65-1") mldsa_ca_key = Fixtures.pkey("mldsa65-2") mldsa_ca_cert = issue_cert(@ca, mldsa_ca_key, 1, @ca_exts, nil, nil, digest: nil) mldsa_cert = issue_cert(@svr, mldsa, 60, [], mldsa_ca_cert, mldsa_ca_key, digest: nil) rsa = Fixtures.pkey("rsa-1") rsa_cert = issue_cert(@svr, rsa, 61, [], @ca_cert, @ca_key) ctx_proc = -> ctx { # Unset values set by start_server ctx.cert = ctx.key = ctx.extra_chain_cert = nil ctx.sigalgs = "rsa_pss_rsae_sha256:mldsa65" ctx.add_certificate(mldsa_cert, mldsa) ctx.add_certificate(rsa_cert, rsa) } server_proc = -> (ctx, ssl) { assert_equal('mldsa65', ssl.sigalg) readwrite_loop(ctx, ssl) } start_server(ctx_proc: ctx_proc, server_proc: server_proc) do |port| ctx = OpenSSL::SSL::SSLContext.new # Set signature algorithm because while OpenSSL may use ML-DSA by # default, the system OpenSSL configuration affects the used signature # algorithm. ctx.sigalgs = 'mldsa65' server_connect(port, ctx) { |ssl| assert_equal('mldsa65', ssl.peer_sigalg) ssl.puts "abc"; ssl.gets } end server_proc = -> (ctx, ssl) { assert_equal('rsa_pss_rsae_sha256', ssl.sigalg) readwrite_loop(ctx, ssl) } start_server(ctx_proc: ctx_proc, server_proc: server_proc) do |port| ctx = OpenSSL::SSL::SSLContext.new ctx.sigalgs = 'rsa_pss_rsae_sha256' server_connect(port, ctx) { |ssl| assert_equal('rsa_pss_rsae_sha256', ssl.peer_sigalg) ssl.puts "abc"; ssl.gets } end end def test_connect_works_when_setting_dh_callback_to_nil omit "AWS-LC does not support DHE ciphersuites" if aws_lc? ctx_proc = -> ctx { ctx.max_version = :TLS1_2 ctx.ciphers = "DH:!NULL" # use DH ctx.tmp_dh_callback = nil } start_server(ctx_proc: ctx_proc) do |port| assert_nothing_raised { server_connect(port) { } } end end def test_tmp_dh # DH missing the q value on unknown named parameters is not FIPS-approved. omit_on_fips omit "AWS-LC does not support DHE ciphersuites" if aws_lc? dh = Fixtures.pkey("dh-1") ctx_proc = -> ctx { ctx.max_version = :TLS1_2 ctx.ciphers = "DH:!NULL" # use DH ctx.tmp_dh = dh } start_server(ctx_proc: ctx_proc) do |port| ctx = OpenSSL::SSL::SSLContext.new ctx.groups = "P-256" # Exclude RFC 7919 groups server_connect(port, ctx) { |ssl| assert_equal dh.to_der, ssl.tmp_key.to_der } end end def test_set_groups_tls12 ctx_proc = -> ctx { # Enable both ECDHE (~ TLS 1.2) cipher suites and TLS 1.3 ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION ctx.ciphers = "kEECDH" ctx.groups = "P-384:P-521" } start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port| # Test 1: Client=P-256:P-384, Server=P-384:P-521 --> P-384 ctx = OpenSSL::SSL::SSLContext.new ctx.groups = "P-256:P-384" server_connect(port, ctx) { |ssl| cs = ssl.cipher[0] assert_match (/\AECDH/), cs # SSL_get0_group_name() is supported on OpenSSL 3.2 or later. assert_equal "secp384r1", ssl.group if openssl?(3, 2, 0) assert_equal "secp384r1", ssl.tmp_key.group.curve_name ssl.puts "abc"; assert_equal "abc\n", ssl.gets } # Test 2: Client=P-256, Server=P-521:P-384 --> Fail ctx = OpenSSL::SSL::SSLContext.new ctx.groups = "P-256" assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx) { } } # Test 3: Client=P-521:P-384, Server=P-521:P-384 --> P-521 ctx = OpenSSL::SSL::SSLContext.new ctx.groups = "P-521:P-384" server_connect(port, ctx) { |ssl| assert_equal "secp521r1", ssl.tmp_key.group.curve_name ssl.puts "abc"; assert_equal "abc\n", ssl.gets } # Test 4: #ecdh_curves= alias ctx = OpenSSL::SSL::SSLContext.new ctx.ecdh_curves = "P-256:P-384" server_connect(port, ctx) { |ssl| assert_equal "secp384r1", ssl.tmp_key.group.curve_name } end end def test_set_groups_tls13 ctx_proc = -> ctx { # Assume TLS 1.3 is enabled and chosen by default ctx.groups = "P-384:P-521" } start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port| ctx = OpenSSL::SSL::SSLContext.new ctx.groups = "P-256:P-384" # disable P-521 server_connect(port, ctx) { |ssl| assert_equal "TLSv1.3", ssl.ssl_version # SSL_get0_group_name() is supported on OpenSSL 3.2 or later. assert_equal "secp384r1", ssl.group if openssl?(3, 2, 0) assert_equal "secp384r1", ssl.tmp_key.group.curve_name ssl.puts "abc"; assert_equal "abc\n", ssl.gets } end end def test_pqc_group # PQC algorithm ML-KEM (FIPS 203) is supported on OpenSSL 3.5 or later. return unless openssl?(3, 5, 0) [ 'X25519MLKEM768', 'SecP256r1MLKEM768', 'SecP384r1MLKEM1024' ].each do |group| ctx_proc = -> ctx { ctx.groups = group } start_server(ctx_proc: ctx_proc) do |port| ctx = OpenSSL::SSL::SSLContext.new ctx.groups = group server_connect(port, ctx) { |ssl| assert_equal(group, ssl.group) ssl.puts "abc"; ssl.gets } end end end def test_security_level ctx = OpenSSL::SSL::SSLContext.new ctx.security_level = 1 if aws_lc? # AWS-LC does not support security levels. assert_equal(0, ctx.security_level) return end assert_equal(1, ctx.security_level) # See SSL_CTX_set_security_level(3). Definitions of security levels may # change in future OpenSSL versions. As of OpenSSL 1.1.0: # - Level 1 requires 160-bit ECC keys or 1024-bit RSA keys. # - Level 2 requires 224-bit ECC keys or 2048-bit RSA keys. begin ec112 = OpenSSL::PKey::EC.generate("secp112r1") ec112_cert = issue_cert(@svr, ec112, 50, [], @ca_cert, @ca_key) ec192 = OpenSSL::PKey::EC.generate("prime192v1") ec192_cert = issue_cert(@svr, ec192, 51, [], @ca_cert, @ca_key) rescue OpenSSL::PKey::PKeyError # Distro-provided OpenSSL may refuse to generate small keys return end assert_raise(OpenSSL::SSL::SSLError) { ctx.add_certificate(ec112_cert, ec112) } assert_nothing_raised { ctx.add_certificate(ec192_cert, ec192) } ctx.security_level = 2 assert_raise(OpenSSL::SSL::SSLError) { # < 112 bits of security ctx.add_certificate(ec192_cert, ec192) } 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) { |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 def test_fileno ctx = OpenSSL::SSL::SSLContext.new sock1, sock2 = socketpair socket = OpenSSL::SSL::SSLSocket.new(sock1) server = OpenSSL::SSL::SSLServer.new(sock2, ctx) assert_equal socket.fileno, socket.to_io.fileno assert_equal server.fileno, server.to_io.fileno ensure sock1.close sock2.close end def test_export_keying_material start_server do |port| cli_ctx = OpenSSL::SSL::SSLContext.new server_connect(port, cli_ctx) do |ssl| assert_instance_of(String, ssl.export_keying_material('ttls keying material', 64)) assert_operator(64, :==, ssl.export_keying_material('ttls keying material', 64).b.length) assert_operator(8, :==, ssl.export_keying_material('ttls keying material', 8).b.length) assert_operator(5, :==, ssl.export_keying_material('test', 5, 'context').b.length) ssl.puts "abc"; ssl.gets # workaround to make tests work on windows end end end # OpenSSL::Buffering requires $/ accessible from non-main Ractors (Ruby 4.0) # https://bugs.ruby-lang.org/issues/21109 # # Hangs on Windows # https://bugs.ruby-lang.org/issues/21537 if respond_to?(:ractor) && RUBY_VERSION >= "4.0" && RUBY_PLATFORM !~ /mswin|mingw/ ractor def test_ractor_client start_server { |port| s = Ractor.new(port, @ca_cert) { |port, ca_cert| sock = TCPSocket.new("127.0.0.1", port) ctx = OpenSSL::SSL::SSLContext.new ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER ctx.cert_store = OpenSSL::X509::Store.new.tap { |store| store.add_cert(ca_cert) } begin ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) ssl.connect ssl.puts("abc") ssl.gets ensure ssl.close sock.close end }.value assert_equal("abc\n", s) } end ractor def test_ractor_set_params # We cannot actually test default stores in the test suite as it depends # on the environment, but at least check that it does not raise an # exception ok = Ractor.new { ctx = OpenSSL::SSL::SSLContext.new ctx.set_params ctx.cert_store.kind_of?(OpenSSL::X509::Store) }.value assert(ok, "ctx.cert_store is an instance of OpenSSL::X509::Store") end end private 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 any of these may be raised assert_raise(OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::EPIPE) { yield } end end end ================================================ FILE: test/openssl/test_ssl_session.rb ================================================ # frozen_string_literal: true require_relative "utils" if defined?(OpenSSL::SSL) class OpenSSL::TestSSLSession < OpenSSL::SSLTestCase def test_session ctx_proc = proc { |ctx| ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION } start_server(ctx_proc: ctx_proc) do |port| server_connect_with_session(port, nil, nil) { |ssl| 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.unpack1('m')) assert_not_nil(session.to_text) } end end # PEM file updated to use TLS 1.2 with ECDHE-RSA-AES256-SHA. DUMMY_SESSION = <<__EOS__ -----BEGIN SSL SESSION PARAMETERS----- MIIDzQIBAQICAwMEAsAUBCAF219w9ZEV8dNA60cpEGOI34hJtIFbf3bkfzSgMyad 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__ # PEM file updated to use TLS 1.1 with ECDHE-RSA-AES256-SHA. DUMMY_SESSION_NO_EXT = <<-__EOS__ -----BEGIN SSL SESSION PARAMETERS----- MIIDCAIBAQICAwIEAsAUBCDyAW7rcpzMjDSosH+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 def test_resumption non_resumable = nil start_server { |port| server_connect_with_session(port, nil, nil) { |ssl| ssl.puts "abc"; assert_equal "abc\n", ssl.gets non_resumable = ssl.session } } ctx_proc = proc { |ctx| ctx.options &= ~OpenSSL::SSL::OP_NO_TICKET # Disable server-side session cache which is enabled by default ctx.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_OFF # Session tickets must be retrieved via ctx.session_new_cb in TLS 1.3 in AWS-LC. ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION if libressl? || aws_lc? } start_server(ctx_proc: ctx_proc) do |port| sess1 = server_connect_with_session(port, nil, nil) { |ssl| ssl.puts("abc"); assert_equal "abc\n", ssl.gets assert_equal false, ssl.session_reused? ssl.session } server_connect_with_session(port, nil, non_resumable) { |ssl| ssl.puts("abc"); assert_equal "abc\n", ssl.gets assert_equal false, ssl.session_reused? } server_connect_with_session(port, nil, sess1) { |ssl| ssl.puts("abc"); assert_equal "abc\n", ssl.gets assert_equal true, ssl.session_reused? } end end def test_server_session_cache ctx_proc = Proc.new do |ctx| ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION ctx.options |= OpenSSL::SSL::OP_NO_TICKET end connections = nil saved_session = nil server_proc = Proc.new do |ctx, ssl| stats = ctx.session_cache_stats case connections when 0 assert_equal false, ssl.session_reused? assert_equal 1, stats[:cache_num] assert_equal 0, stats[:cache_hits] assert_equal 0, stats[:cache_misses] when 1 assert_equal true, ssl.session_reused? assert_equal 1, stats[:cache_num] assert_equal 1, stats[:cache_hits] assert_equal 0, stats[:cache_misses] saved_session = ssl.session assert_equal true, ctx.session_remove(ssl.session) when 2 assert_equal false, ssl.session_reused? assert_equal 1, stats[:cache_num] assert_equal 1, stats[:cache_hits] assert_equal 1, stats[:cache_misses] assert_equal true, ctx.session_add(saved_session.dup) when 3 assert_equal true, ssl.session_reused? assert_equal 2, stats[:cache_num] assert_equal 2, stats[:cache_hits] assert_equal 1, stats[:cache_misses] ctx.flush_sessions(Time.now + 10000) when 4 assert_equal false, ssl.session_reused? assert_equal 1, stats[:cache_num] assert_equal 2, stats[:cache_hits] assert_equal 2, stats[:cache_misses] assert_equal true, ctx.session_add(saved_session.dup) end readwrite_loop(ctx, ssl) end start_server(ctx_proc: ctx_proc, server_proc: server_proc) do |port| first_session = nil 10.times do |i| connections = i cctx = OpenSSL::SSL::SSLContext.new cctx.max_version = OpenSSL::SSL::TLS1_2_VERSION server_connect_with_session(port, cctx, first_session) { |ssl| ssl.puts("abc"); assert_equal "abc\n", ssl.gets first_session ||= ssl.session case connections when 0; when 1; assert_equal true, ssl.session_reused? when 2; assert_equal false, ssl.session_reused? when 3; assert_equal true, ssl.session_reused? when 4; assert_equal false, ssl.session_reused? when 5..9; assert_equal true, ssl.session_reused? end } end end end # Skipping tests that use session_remove_cb by default because it may cause # deadlock. TEST_SESSION_REMOVE_CB = ENV["OSSL_TEST_UNSAFE"] == "1" def test_ctx_client_session_cb_tls12 start_server do |port| called = {} ctx = OpenSSL::SSL::SSLContext.new ctx.min_version = ctx.max_version = :TLS1_2 ctx.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT ctx.session_new_cb = lambda { |ary| sock, sess = ary called[:new] = [sock, sess] } if TEST_SESSION_REMOVE_CB ctx.session_remove_cb = lambda { |ary| ctx, sess = ary called[:remove] = [ctx, sess] } end server_connect_with_session(port, ctx, nil) { |ssl| assert_equal(1, ctx.session_cache_stats[:connect_good]) assert_equal([ssl, ssl.session], called[:new]) # AWS-LC doesn't support internal session caching on the client, but # the callback is still enabled as expected. unless aws_lc? assert_equal(1, ctx.session_cache_stats[:cache_num]) assert_equal(true, ctx.session_remove(ssl.session)) if TEST_SESSION_REMOVE_CB assert_equal([ctx, ssl.session], called[:remove]) end end assert_equal(false, ctx.session_remove(ssl.session)) } end end def test_ctx_client_session_cb_tls13 omit "LibreSSL does not call session_new_cb in TLS 1.3" if libressl? omit "AWS-LC does not support internal session caching on the client" if aws_lc? start_server do |port| called = {} ctx = OpenSSL::SSL::SSLContext.new ctx.min_version = :TLS1_3 ctx.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT ctx.session_new_cb = lambda { |ary| sock, sess = ary called[:new] = [sock, sess] } server_connect_with_session(port, ctx, nil) { |ssl| ssl.puts("abc"); assert_equal("abc\n", ssl.gets) assert_operator(1, :<=, ctx.session_cache_stats[:cache_num]) assert_operator(1, :<=, ctx.session_cache_stats[:connect_good]) assert_equal([ssl, ssl.session], called[:new]) } end end def test_ctx_client_session_cb_tls13_exception omit "LibreSSL does not call session_new_cb in TLS 1.3" if libressl? server_proc = lambda do |ctx, ssl| readwrite_loop(ctx, ssl) rescue SystemCallError, OpenSSL::SSL::SSLError end start_server(server_proc: server_proc) do |port| ctx = OpenSSL::SSL::SSLContext.new ctx.min_version = :TLS1_3 ctx.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT ctx.session_new_cb = lambda { |ary| raise "in session_new_cb" } server_connect_with_session(port, ctx, nil) { |ssl| assert_raise_with_message(RuntimeError, /in session_new_cb/) { ssl.puts("abc"); assert_equal("abc\n", ssl.gets) } } end end def test_ctx_server_session_cb connections = nil called = {} cctx = OpenSSL::SSL::SSLContext.new cctx.max_version = OpenSSL::SSL::TLS1_2_VERSION sctx = nil ctx_proc = Proc.new { |ctx| sctx = ctx ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION ctx.options |= OpenSSL::SSL::OP_NO_TICKET # get_cb is called whenever a client proposed to resume a session but # the session could not be found in the internal session cache. last_server_session = nil ctx.session_get_cb = lambda { |ary| _sess, data = ary called[:get] = data if connections == 2 last_server_session.dup else nil end } ctx.session_new_cb = lambda { |ary| _sock, sess = ary called[:new] = sess last_server_session = sess } if TEST_SESSION_REMOVE_CB ctx.session_remove_cb = lambda { |ary| _ctx, sess = ary called[:remove] = sess } end } start_server(ctx_proc: ctx_proc) do |port| connections = 0 sess0 = server_connect_with_session(port, cctx, nil) { |ssl| ssl.puts("abc"); assert_equal "abc\n", ssl.gets assert_equal false, ssl.session_reused? ssl.session } assert_nil called[:get] assert_not_nil called[:new] assert_equal sess0.id, called[:new].id if TEST_SESSION_REMOVE_CB assert_nil called[:remove] end called.clear # Internal cache hit connections = 1 server_connect_with_session(port, cctx, sess0.dup) { |ssl| ssl.puts("abc"); assert_equal "abc\n", ssl.gets assert_equal true, ssl.session_reused? ssl.session } assert_nil called[:get] assert_nil called[:new] if TEST_SESSION_REMOVE_CB assert_nil called[:remove] end called.clear sctx.flush_sessions(Time.now + 10000) if TEST_SESSION_REMOVE_CB assert_not_nil called[:remove] assert_equal sess0.id, called[:remove].id end called.clear # External cache hit connections = 2 sess2 = server_connect_with_session(port, cctx, sess0.dup) { |ssl| ssl.puts("abc"); assert_equal "abc\n", ssl.gets assert_equal true, ssl.session_reused? ssl.session } assert_equal sess0.id, sess2.id assert_equal sess0.id, called[:get] assert_nil called[:new] if TEST_SESSION_REMOVE_CB assert_nil called[:remove] end called.clear sctx.flush_sessions(Time.now + 10000) if TEST_SESSION_REMOVE_CB assert_not_nil called[:remove] assert_equal sess0.id, called[:remove].id end called.clear # Cache miss connections = 3 sess3 = server_connect_with_session(port, cctx, sess0.dup) { |ssl| ssl.puts("abc"); assert_equal "abc\n", ssl.gets assert_equal false, ssl.session_reused? ssl.session } assert_not_equal sess0.id, sess3.id assert_equal sess0.id, called[:get] assert_not_nil called[:new] assert_equal sess3.id, called[:new].id if TEST_SESSION_REMOVE_CB assert_nil called[:remove] end end 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 private def server_connect_with_session(port, ctx = nil, sess = nil) sock = TCPSocket.new("127.0.0.1", port) ctx ||= OpenSSL::SSL::SSLContext.new ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) ssl.session = sess if sess ssl.sync_close = true ssl.connect yield ssl if block_given? ensure ssl&.close sock&.close end end end ================================================ FILE: test/openssl/test_ts.rb ================================================ require_relative "utils" if defined?(OpenSSL) && defined?(OpenSSL::Timestamp) class OpenSSL::TestTimestamp < OpenSSL::TestCase def intermediate_key @intermediate_key ||= OpenSSL::PKey::RSA.new <<-_end_of_pem_ -----BEGIN RSA PRIVATE KEY----- MIICWwIBAAKBgQCcyODxH+oTrr7l7MITWcGaYnnBma6vidCCJjuSzZpaRmXZHAyH 0YcY4ttC0BdJ4uV+cE05IySVC7tyvVfFb8gFQ6XJV+AEktP+XkLbcxZgj9d2NVu1 ziXdI+ldXkPnMhyWpMS5E7SD6gflv9NhUYEsmAGsUgdK6LDmm2W2/4TlewIDAQAB AoGAYgx6KDFWONLqjW3f/Sv/mGYHUNykUyDzpcD1Npyf797gqMMSzwlo3FZa2tC6 D7n23XirwpTItvEsW9gvgMikJDPlThAeGLZ+L0UbVNNBHVxGP998Nda1kxqKvhRE pfZCKc7PLM9ZXc6jBTmgxdcAYfVCCVUoa2mEf9Ktr3BlI4kCQQDQAM09+wHDXGKP o2UnCwCazGtyGU2r0QCzHlh9BVY+KD2KjjhuWh86rEbdWN7hEW23Je1vXIhuM6Pa /Ccd+XYnAkEAwPZ91PK6idEONeGQ4I3dyMKV2SbaUjfq3MDL4iIQPQPuj7QsBO/5 3Nf9ReSUUTRFCUVwoC8k4Z1KAJhR/K/ejQJANE7PTnPuGJQGETs09+GTcFpR9uqY FspDk8fg1ufdrVnvSAXF+TJewiGK3KU5v33jinhWQngRsyz3Wt2odKhEZwJACbjh oicQqvzzgFd7GzVKpWDYd/ZzLY1PsgusuhoJQ2m9TVRAm4cTycLAKhNYPbcqe0sa X5fAffWU0u7ZwqeByQJAOUAbYET4RU3iymAvAIDFj8LiQnizG9t5Ty3HXlijKQYv y8gsvWd4CdxwOPatWpBUX9L7IXcMJmD44xXTUvpbfQ== -----END RSA PRIVATE KEY----- _end_of_pem_ end def ee_key @ee_key ||= OpenSSL::PKey::RSA.new <<-_end_of_pem_ -----BEGIN RSA PRIVATE KEY----- MIICWwIBAAKBgQDA6eB5r2O5KOKNbKMBhzadl43lgpwqq28m+G0gH38kKCL1f3o9 P8xUZm7sZqcWEervZMSSXMGBV9DgeoSR+U6FMJywgQGx/JNRx7wZTMNym3PvgLkl xCXh6ZA0/xbtJtcNI+UUv0ENBkTIuUWBhkAf3jQclAr9aQ0ktYBuHAcRcQIDAQAB AoGAKNhcAuezwZx6e18pFEXAtpVEIfgJgK9TlXi8AjUpAkrNPBWFmDpN1QDrM3p4 nh+lEpLPW/3vqqchPqYyM4YJraMLpS3KUG+s7+m9QIia0ri2WV5Cig7WL+Tl9p7K b3oi2Aj/wti8GfOLFQXOQQ4Ea4GoCv2Sxe0GZR39UBxzTsECQQD1zuVIwBvqU2YR 8innsoa+j4u2hulRmQO6Zgpzj5vyRYfA9uZxQ9nKbfJvzuWwUv+UzyS9RqxarqrP 5nQw5EmVAkEAyOmJg6+AfGrgvSWfSpXEds/WA/sHziCO3rE4/sd6cnDc6XcTgeMs mT8Z3kAYGpqFDew5orUylPfJJa+PUueJbQJAY+gkvw3+Cp69FLw1lgu0wo07fwOU n2qu3jsNMm0DOFRUWfTAMvcd9S385L7WEnWZldUfnKK1+OGXYYrMXPbchQJAChU2 UoaHQzc16iguM1cK0g+iJPb/MEgQA3sPajHmokGpxIm2T+lvvo0dJjs/Om6QyN8X EWRYkoNQ8/Q4lCeMjQJAfvDIGtyqF4PieFHYgluQAv5pGgYpakdc8SYyeRH9NKey GaL27FRs4fRWf9OmxPhUVgIyGzLGXrueemvQUDHObA== -----END RSA PRIVATE KEY----- _end_of_pem_ end def ca_cert @ca_cert ||= OpenSSL::Certs.ca_cert end def ca_store @ca_store ||= OpenSSL::X509::Store.new.tap { |s| s.add_cert(ca_cert) } end def ts_cert_direct @ts_cert_direct ||= OpenSSL::Certs.ts_cert_direct(ee_key, ca_cert) end def intermediate_cert @intermediate_cert ||= OpenSSL::Certs.intermediate_cert(intermediate_key, ca_cert) end def intermediate_store @intermediate_store ||= OpenSSL::X509::Store.new.tap { |s| s.add_cert(intermediate_cert) } end def ts_cert_ee @ts_cert_ee ||= OpenSSL::Certs.ts_cert_ee(ee_key, intermediate_cert, intermediate_key) end def test_request_mandatory_fields req = OpenSSL::Timestamp::Request.new assert_raise(OpenSSL::Timestamp::TimestampError) do req.to_der end req.algorithm = "sha1" assert_raise(OpenSSL::Timestamp::TimestampError) do req.to_der end req.message_imprint = OpenSSL::Digest.digest('SHA1', "data") assert_nothing_raised { req.to_der } end def test_request_assignment req = OpenSSL::Timestamp::Request.new req.version = 2 assert_equal(2, req.version) assert_raise(TypeError) { req.version = nil } assert_raise(TypeError) { req.version = "foo" } req.algorithm = "sha1" assert_equal("SHA1", req.algorithm) assert_equal("SHA1", OpenSSL::ASN1.ObjectId("SHA1").sn) assert_raise(TypeError) { req.algorithm = nil } assert_raise(OpenSSL::ASN1::ASN1Error) { req.algorithm = "xxx" } req.message_imprint = "test" assert_equal("test", req.message_imprint) assert_raise(TypeError) { req.message_imprint = nil } req.policy_id = "1.2.3.4.5" assert_equal("1.2.3.4.5", req.policy_id) assert_raise(TypeError) { req.policy_id = 123 } assert_raise(TypeError) { req.policy_id = nil } req.nonce = 42 assert_equal(42, req.nonce) assert_raise(TypeError) { req.nonce = "foo" } assert_raise(TypeError) { req.nonce = nil } req.cert_requested = false assert_equal(false, req.cert_requested?) req.cert_requested = nil assert_equal(false, req.cert_requested?) req.cert_requested = 123 assert_equal(true, req.cert_requested?) req.cert_requested = "asdf" assert_equal(true, req.cert_requested?) end def test_request_serialization req = OpenSSL::Timestamp::Request.new req.version = 2 req.algorithm = "SHA1" req.message_imprint = "test" req.policy_id = "1.2.3.4.5" req.nonce = 42 req.cert_requested = true req = OpenSSL::Timestamp::Request.new(req.to_der) assert_equal(2, req.version) assert_equal("SHA1", req.algorithm) assert_equal("test", req.message_imprint) assert_equal("1.2.3.4.5", req.policy_id) assert_equal(42, req.nonce) assert_equal(true, req.cert_requested?) end def test_request_re_assignment #tests whether the potential 'freeing' of previous values in C works properly req = OpenSSL::Timestamp::Request.new req.version = 2 req.version = 3 req.algorithm = "SHA1" req.algorithm = "SHA256" req.message_imprint = "test" req.message_imprint = "test2" req.policy_id = "1.2.3.4.5" req.policy_id = "1.2.3.4.6" req.nonce = 42 req.nonce = 24 req.cert_requested = false req.cert_requested = true req.to_der end def test_request_encode_decode req = OpenSSL::Timestamp::Request.new req.algorithm = "SHA1" digest = OpenSSL::Digest.digest('SHA1', "test") req.message_imprint = digest req.policy_id = "1.2.3.4.5" req.nonce = 42 qer = OpenSSL::Timestamp::Request.new(req.to_der) assert_equal(1, qer.version) assert_equal("SHA1", qer.algorithm) assert_equal(digest, qer.message_imprint) assert_equal("1.2.3.4.5", qer.policy_id) assert_equal(42, qer.nonce) #put OpenSSL::ASN1.decode inbetween qer2 = OpenSSL::Timestamp::Request.new(OpenSSL::ASN1.decode(req.to_der)) assert_equal(1, qer2.version) assert_equal("SHA1", qer2.algorithm) assert_equal(digest, qer2.message_imprint) assert_equal("1.2.3.4.5", qer2.policy_id) assert_equal(42, qer2.nonce) end def test_request_invalid_asn1 assert_raise(OpenSSL::Timestamp::TimestampError) do OpenSSL::Timestamp::Request.new("*" * 44) end end def test_response_constants assert_equal(0, OpenSSL::Timestamp::Response::GRANTED) assert_equal(1, OpenSSL::Timestamp::Response::GRANTED_WITH_MODS) assert_equal(2, OpenSSL::Timestamp::Response::REJECTION) assert_equal(3, OpenSSL::Timestamp::Response::WAITING) assert_equal(4, OpenSSL::Timestamp::Response::REVOCATION_WARNING) assert_equal(5, OpenSSL::Timestamp::Response::REVOCATION_NOTIFICATION) end def test_response_creation req = OpenSSL::Timestamp::Request.new req.algorithm = "SHA1" digest = OpenSSL::Digest.digest('SHA1', "test") req.message_imprint = digest req.policy_id = "1.2.3.4.5" fac = OpenSSL::Timestamp::Factory.new time = Time.now fac.gen_time = time fac.serial_number = 1 fac.allowed_digests = ["sha1"] resp = fac.create_timestamp(ee_key, ts_cert_ee, req) resp = OpenSSL::Timestamp::Response.new(resp) assert_equal(OpenSSL::Timestamp::Response::GRANTED, resp.status) assert_nil(resp.failure_info) assert_equal([], resp.status_text) assert_equal(1, resp.token_info.version) assert_equal("1.2.3.4.5", resp.token_info.policy_id) assert_equal("SHA1", resp.token_info.algorithm) assert_equal(digest, resp.token_info.message_imprint) assert_equal(1, resp.token_info.serial_number) assert_equal(time.to_i, resp.token_info.gen_time.to_i) assert_equal(false, resp.token_info.ordering) assert_nil(resp.token_info.nonce) assert_cert(ts_cert_ee, resp.tsa_certificate) #compare PKCS7 token = OpenSSL::ASN1.decode(resp.to_der).value[1] assert_equal(token.to_der, resp.token.to_der) end def test_response_failure_info resp = OpenSSL::Timestamp::Response.new("0\"0 \x02\x01\x020\x17\f\x15Invalid TimeStampReq.\x03\x02\x06\x80") assert_equal(:BAD_ALG, resp.failure_info) end def test_response_mandatory_fields fac = OpenSSL::Timestamp::Factory.new req = OpenSSL::Timestamp::Request.new assert_raise(OpenSSL::Timestamp::TimestampError) do fac.create_timestamp(ee_key, ts_cert_ee, req) end req.algorithm = "sha1" assert_raise(OpenSSL::Timestamp::TimestampError) do fac.create_timestamp(ee_key, ts_cert_ee, req) end req.message_imprint = OpenSSL::Digest.digest('SHA1', "data") assert_raise(OpenSSL::Timestamp::TimestampError) do fac.create_timestamp(ee_key, ts_cert_ee, req) end fac.gen_time = Time.now assert_raise(OpenSSL::Timestamp::TimestampError) do fac.create_timestamp(ee_key, ts_cert_ee, req) end fac.serial_number = 1 fac.allowed_digests = ["sha1"] assert_raise(OpenSSL::Timestamp::TimestampError) do fac.create_timestamp(ee_key, ts_cert_ee, req) end fac.default_policy_id = "1.2.3.4.5" assert_equal OpenSSL::Timestamp::Response::GRANTED, fac.create_timestamp(ee_key, ts_cert_ee, req).status fac.default_policy_id = nil assert_raise(OpenSSL::Timestamp::TimestampError) do fac.create_timestamp(ee_key, ts_cert_ee, req) end req.policy_id = "1.2.3.4.5" assert_equal OpenSSL::Timestamp::Response::GRANTED, fac.create_timestamp(ee_key, ts_cert_ee, req).status end def test_response_allowed_digests req = OpenSSL::Timestamp::Request.new req.algorithm = "SHA1" req.message_imprint = OpenSSL::Digest.digest('SHA1', "test") fac = OpenSSL::Timestamp::Factory.new fac.gen_time = Time.now fac.serial_number = 1 fac.default_policy_id = "1.2.3.4.6" # None allowed by default resp = fac.create_timestamp(ee_key, ts_cert_ee, req) assert_equal OpenSSL::Timestamp::Response::REJECTION, resp.status # Explicitly allow SHA1 (string) fac.allowed_digests = ["sha1"] resp = fac.create_timestamp(ee_key, ts_cert_ee, req) assert_equal OpenSSL::Timestamp::Response::GRANTED, resp.status # Explicitly allow SHA1 (object) fac.allowed_digests = [OpenSSL::Digest.new('SHA1')] resp = fac.create_timestamp(ee_key, ts_cert_ee, req) assert_equal OpenSSL::Timestamp::Response::GRANTED, resp.status # Others not allowed req.algorithm = "SHA256" req.message_imprint = OpenSSL::Digest.digest('SHA256', "test") resp = fac.create_timestamp(ee_key, ts_cert_ee, req) assert_equal OpenSSL::Timestamp::Response::REJECTION, resp.status # Non-Array fac.allowed_digests = 123 resp = fac.create_timestamp(ee_key, ts_cert_ee, req) assert_equal OpenSSL::Timestamp::Response::REJECTION, resp.status # Non-String, non-Digest Array element fac.allowed_digests = ["sha1", OpenSSL::Digest.new('SHA1'), 123] assert_raise(TypeError) do fac.create_timestamp(ee_key, ts_cert_ee, req) end end def test_response_default_policy req = OpenSSL::Timestamp::Request.new req.algorithm = "SHA1" digest = OpenSSL::Digest.digest('SHA1', "test") req.message_imprint = digest fac = OpenSSL::Timestamp::Factory.new fac.gen_time = Time.now fac.serial_number = 1 fac.allowed_digests = ["sha1"] fac.default_policy_id = "1.2.3.4.6" resp = fac.create_timestamp(ee_key, ts_cert_ee, req) assert_equal(OpenSSL::Timestamp::Response::GRANTED, resp.status) assert_equal("1.2.3.4.6", resp.token_info.policy_id) assert_match(/1\.2\.3\.4\.6/, resp.to_text) end def test_response_bad_purpose req = OpenSSL::Timestamp::Request.new req.algorithm = "SHA1" digest = OpenSSL::Digest.digest('SHA1', "test") req.message_imprint = digest req.policy_id = "1.2.3.4.5" req.nonce = 42 fac = OpenSSL::Timestamp::Factory.new fac.gen_time = Time.now fac.serial_number = 1 fac.allowed_digests = ["sha1"] assert_raise(OpenSSL::Timestamp::TimestampError) do fac.create_timestamp(ee_key, intermediate_cert, req) end end def test_response_invalid_asn1 assert_raise(OpenSSL::Timestamp::TimestampError) do OpenSSL::Timestamp::Response.new("*" * 44) end end def test_no_cert_requested req = OpenSSL::Timestamp::Request.new req.algorithm = "SHA1" digest = OpenSSL::Digest.digest('SHA1', "test") req.message_imprint = digest req.cert_requested = false fac = OpenSSL::Timestamp::Factory.new fac.gen_time = Time.now fac.serial_number = 1 fac.allowed_digests = ["sha1"] fac.default_policy_id = "1.2.3.4.5" resp = fac.create_timestamp(ee_key, ts_cert_ee, req) assert_equal(OpenSSL::Timestamp::Response::GRANTED, resp.status) assert_nil(resp.tsa_certificate) end def test_response_no_policy_defined req = OpenSSL::Timestamp::Request.new req.algorithm = "SHA1" digest = OpenSSL::Digest.digest('SHA1', "test") req.message_imprint = digest fac = OpenSSL::Timestamp::Factory.new fac.gen_time = Time.now fac.serial_number = 1 fac.allowed_digests = ["sha1"] assert_raise(OpenSSL::Timestamp::TimestampError) do fac.create_timestamp(ee_key, ts_cert_ee, req) end end def test_verify_ee_no_req ts, _ = timestamp_ee assert_raise(TypeError) do ts.verify(nil, ca_cert) end end def test_verify_ee_no_store ts, req = timestamp_ee assert_raise(TypeError) do ts.verify(req, nil) end end def test_verify_ee_wrong_root_no_intermediate ts, req = timestamp_ee assert_raise(OpenSSL::Timestamp::TimestampError) do ts.verify(req, intermediate_store) end end def test_verify_ee_wrong_root_wrong_intermediate ts, req = timestamp_ee assert_raise(OpenSSL::Timestamp::TimestampError) do ts.verify(req, intermediate_store, [ca_cert]) end end def test_verify_ee_nonce_mismatch ts, req = timestamp_ee req.nonce = 1 assert_raise(OpenSSL::Timestamp::TimestampError) do ts.verify(req, ca_store, [intermediate_cert]) end end def test_verify_ee_intermediate_missing ts, req = timestamp_ee assert_raise(OpenSSL::Timestamp::TimestampError) do ts.verify(req, ca_store) end end def test_verify_ee_intermediate ts, req = timestamp_ee ts.verify(req, ca_store, [intermediate_cert]) end def test_verify_ee_intermediate_type_error ts, req = timestamp_ee assert_raise(TypeError) { ts.verify(req, [ca_cert], 123) } end def test_verify_ee_def_policy req = OpenSSL::Timestamp::Request.new req.algorithm = "SHA1" digest = OpenSSL::Digest.digest('SHA1', "test") req.message_imprint = digest req.nonce = 42 fac = OpenSSL::Timestamp::Factory.new fac.gen_time = Time.now fac.serial_number = 1 fac.allowed_digests = ["sha1"] fac.default_policy_id = "1.2.3.4.5" ts = fac.create_timestamp(ee_key, ts_cert_ee, req) ts.verify(req, ca_store, [intermediate_cert]) end def test_verify_direct ts, req = timestamp_direct ts.verify(req, ca_store) end def test_verify_direct_redundant_untrusted ts, req = timestamp_direct ts.verify(req, ca_store, [ts.tsa_certificate, ts.tsa_certificate]) end def test_verify_direct_unrelated_untrusted ts, req = timestamp_direct ts.verify(req, ca_store, [intermediate_cert]) end def test_verify_direct_wrong_root ts, req = timestamp_direct assert_raise(OpenSSL::Timestamp::TimestampError) do ts.verify(req, intermediate_store) end end def test_verify_direct_no_cert_no_intermediate ts, req = timestamp_direct_no_cert assert_raise(OpenSSL::Timestamp::TimestampError) do ts.verify(req, ca_store) end end def test_verify_ee_no_cert ts, req = timestamp_ee_no_cert assert_same(ts, ts.verify(req, ca_store, [ts_cert_ee, intermediate_cert])) end def test_verify_ee_no_cert_no_intermediate ts, req = timestamp_ee_no_cert assert_raise(OpenSSL::Timestamp::TimestampError) do ts.verify(req, ca_store, [ts_cert_ee]) end end def test_verify_ee_additional_certs_array req = OpenSSL::Timestamp::Request.new req.algorithm = "SHA1" digest = OpenSSL::Digest.digest('SHA1', "test") req.message_imprint = digest req.policy_id = "1.2.3.4.5" req.nonce = 42 fac = OpenSSL::Timestamp::Factory.new fac.gen_time = Time.now fac.serial_number = 1 fac.allowed_digests = ["sha1"] fac.additional_certs = [intermediate_cert] ts = fac.create_timestamp(ee_key, ts_cert_ee, req) assert_equal(2, ts.token.certificates.size) fac.additional_certs = nil ts.verify(req, ca_store) ts = fac.create_timestamp(ee_key, ts_cert_ee, req) assert_equal(1, ts.token.certificates.size) end def test_verify_ee_additional_certs_with_root req = OpenSSL::Timestamp::Request.new req.algorithm = "SHA1" digest = OpenSSL::Digest.digest('SHA1', "test") req.message_imprint = digest req.policy_id = "1.2.3.4.5" req.nonce = 42 fac = OpenSSL::Timestamp::Factory.new fac.gen_time = Time.now fac.serial_number = 1 fac.allowed_digests = ["sha1"] fac.additional_certs = [intermediate_cert, ca_cert] ts = fac.create_timestamp(ee_key, ts_cert_ee, req) assert_equal(3, ts.token.certificates.size) ts.verify(req, ca_store) end def test_verify_ee_cert_inclusion_not_requested req = OpenSSL::Timestamp::Request.new req.algorithm = "SHA1" digest = OpenSSL::Digest.digest('SHA1', "test") req.message_imprint = digest req.nonce = 42 req.cert_requested = false fac = OpenSSL::Timestamp::Factory.new fac.gen_time = Time.now fac.serial_number = 1 fac.allowed_digests = ["sha1"] #needed because the Request contained no policy identifier fac.default_policy_id = '1.2.3.4.5' fac.additional_certs = [ ts_cert_ee, intermediate_cert ] ts = fac.create_timestamp(ee_key, ts_cert_ee, req) assert_nil(ts.token.certificates) #since cert_requested? == false ts.verify(req, ca_store, [ts_cert_ee, intermediate_cert]) end def test_reusable #test if req and faq are reusable, i.e. the internal #CTX_free methods don't mess up e.g. the certificates req = OpenSSL::Timestamp::Request.new req.algorithm = "SHA1" digest = OpenSSL::Digest.digest('SHA1', "test") req.message_imprint = digest req.policy_id = "1.2.3.4.5" req.nonce = 42 fac = OpenSSL::Timestamp::Factory.new fac.gen_time = Time.now fac.serial_number = 1 fac.allowed_digests = ["sha1"] fac.additional_certs = [ intermediate_cert ] ts1 = fac.create_timestamp(ee_key, ts_cert_ee, req) ts1.verify(req, ca_store) ts2 = fac.create_timestamp(ee_key, ts_cert_ee, req) ts2.verify(req, ca_store) refute_nil(ts1.tsa_certificate) refute_nil(ts2.tsa_certificate) end def test_token_info_creation req = OpenSSL::Timestamp::Request.new req.algorithm = "SHA1" digest = OpenSSL::Digest.digest('SHA1', "test") req.message_imprint = digest req.policy_id = "1.2.3.4.5" req.nonce = OpenSSL::BN.new(123) fac = OpenSSL::Timestamp::Factory.new time = Time.now fac.gen_time = time fac.serial_number = 1 fac.allowed_digests = ["sha1"] resp = fac.create_timestamp(ee_key, ts_cert_ee, req) info = resp.token_info info = OpenSSL::Timestamp::TokenInfo.new(info.to_der) assert_equal(1, info.version) assert_equal("1.2.3.4.5", info.policy_id) assert_equal("SHA1", info.algorithm) assert_equal(digest, info.message_imprint) assert_equal(1, info.serial_number) assert_equal(time.to_i, info.gen_time.to_i) assert_equal(false, info.ordering) assert_equal(123, info.nonce) end def test_token_info_invalid_asn1 assert_raise(OpenSSL::Timestamp::TimestampError) do OpenSSL::Timestamp::TokenInfo.new("*" * 44) end end private def assert_cert expected, actual assert_equal expected.to_der, actual.to_der end def timestamp_ee req = OpenSSL::Timestamp::Request.new req.algorithm = "SHA1" digest = OpenSSL::Digest.digest('SHA1', "test") req.message_imprint = digest req.policy_id = "1.2.3.4.5" req.nonce = 42 fac = OpenSSL::Timestamp::Factory.new fac.gen_time = Time.now fac.serial_number = 1 fac.allowed_digests = ["sha1"] return fac.create_timestamp(ee_key, ts_cert_ee, req), req end def timestamp_ee_no_cert req = OpenSSL::Timestamp::Request.new req.algorithm = "SHA1" digest = OpenSSL::Digest.digest('SHA1', "test") req.message_imprint = digest req.policy_id = "1.2.3.4.5" req.nonce = 42 req.cert_requested = false fac = OpenSSL::Timestamp::Factory.new fac.gen_time = Time.now fac.serial_number = 1 fac.allowed_digests = ["sha1"] return fac.create_timestamp(ee_key, ts_cert_ee, req), req end def timestamp_direct req = OpenSSL::Timestamp::Request.new req.algorithm = "SHA1" digest = OpenSSL::Digest.digest('SHA1', "test") req.message_imprint = digest req.policy_id = "1.2.3.4.5" req.nonce = 42 fac = OpenSSL::Timestamp::Factory.new fac.gen_time = Time.now fac.serial_number = 1 fac.allowed_digests = ["sha1"] return fac.create_timestamp(ee_key, ts_cert_direct, req), req end def timestamp_direct_no_cert req = OpenSSL::Timestamp::Request.new req.algorithm = "SHA1" digest = OpenSSL::Digest.digest('SHA1', "test") req.message_imprint = digest req.policy_id = "1.2.3.4.5" req.nonce = 42 req.cert_requested = false fac = OpenSSL::Timestamp::Factory.new fac.gen_time = Time.now fac.serial_number = 1 fac.allowed_digests = ["sha1"] return fac.create_timestamp(ee_key, ts_cert_direct, req), req end end end ================================================ FILE: test/openssl/test_x509attr.rb ================================================ # frozen_string_literal: true require_relative "utils" if defined?(OpenSSL) 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_raise(OpenSSL::X509::AttributeError) { v = OpenSSL::ASN1::Set([OpenSSL::ASN1::UTF8String("1234")], 17, :EXPLICIT) attr.value = v } 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 def test_eq val1 = OpenSSL::ASN1::Set([ OpenSSL::ASN1::UTF8String("abc123") ]) attr1 = OpenSSL::X509::Attribute.new("challengePassword", val1) attr2 = OpenSSL::X509::Attribute.new("challengePassword", val1) ef = OpenSSL::X509::ExtensionFactory.new val2 = OpenSSL::ASN1::Set.new([OpenSSL::ASN1::Sequence.new([ ef.create_extension("keyUsage", "keyCertSign", true) ])]) attr3 = OpenSSL::X509::Attribute.new("extReq", val2) assert_equal false, attr1 == 12345 assert_equal true, attr1 == attr2 assert_equal false, attr1 == attr3 end def test_marshal val = OpenSSL::ASN1::Set([ OpenSSL::ASN1::UTF8String("abc123") ]) attr = OpenSSL::X509::Attribute.new("challengePassword", val) deserialized = Marshal.load(Marshal.dump(attr)) assert_equal attr.to_der, deserialized.to_der end end end ================================================ FILE: test/openssl/test_x509cert.rb ================================================ # frozen_string_literal: true require_relative "utils" if defined?(OpenSSL) class OpenSSL::TestX509Certificate < OpenSSL::TestCase def setup super @rsa1 = Fixtures.pkey("rsa-1") @rsa2 = Fixtures.pkey("rsa-2") @ec1 = Fixtures.pkey("p256") @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 test_serial [1, 2**32, 2**100].each{|s| cert = issue_cert(@ca, @rsa1, 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], ] cert = issue_cert(@ca, @rsa1, 1, exts, nil, nil) assert_kind_of(OpenSSL::PKey::RSA, cert.public_key) assert_equal(@rsa1.public_to_der, cert.public_key.public_to_der) cert = OpenSSL::X509::Certificate.new(cert.to_der) assert_equal(@rsa1.public_to_der, cert.public_key.public_to_der) end def test_validity now = Time.at(Time.now.to_i + 0.9) cert = issue_cert(@ca, @rsa1, 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, @rsa1, 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, @rsa1, 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, @rsa1, 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_factory ca_exts = [ ["basicConstraints","CA:TRUE",true], ["keyUsage","keyCertSign, cRLSign",true], ["subjectKeyIdentifier","hash",false], ["authorityKeyIdentifier","issuer:always,keyid:always",false], ] ca_cert = issue_cert(@ca, @rsa1, 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","issuer:always,keyid:always",false], ["extendedKeyUsage","clientAuth, emailProtection, codeSigning",false], ["subjectAltName","email:ee1@ruby-lang.org",false], ] ee1_cert = issue_cert(@ee1, @rsa2, 2, ee1_exts, ca_cert, @rsa1) 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_akiski ca_cert = generate_cert(@ca, @rsa1, 4, nil) ef = OpenSSL::X509::ExtensionFactory.new(ca_cert, ca_cert) ca_cert.add_extension( ef.create_extension("subjectKeyIdentifier", "hash", false)) ca_cert.add_extension( ef.create_extension("authorityKeyIdentifier", "issuer:always,keyid:always", false)) ca_cert.sign(@rsa1, "sha256") ca_keyid = get_subject_key_id(ca_cert.to_der, hex: false) assert_equal ca_keyid, ca_cert.authority_key_identifier assert_equal ca_keyid, ca_cert.subject_key_identifier ee_cert = generate_cert(@ee1, @rsa2, 5, ca_cert) ef = OpenSSL::X509::ExtensionFactory.new(ca_cert, ee_cert) ee_cert.add_extension( ef.create_extension("subjectKeyIdentifier", "hash", false)) ee_cert.add_extension( ef.create_extension("authorityKeyIdentifier", "issuer:always,keyid:always", false)) ee_cert.sign(@rsa1, "sha256") ee_keyid = get_subject_key_id(ee_cert.to_der, hex: false) assert_equal ca_keyid, ee_cert.authority_key_identifier assert_equal ee_keyid, ee_cert.subject_key_identifier end def test_akiski_missing cert = issue_cert(@ee1, @rsa1, 1, [], nil, nil) assert_nil(cert.authority_key_identifier) assert_nil(cert.subject_key_identifier) end def test_crl_uris_no_crl_distribution_points cert = issue_cert(@ee1, @rsa1, 1, [], nil, nil) assert_nil(cert.crl_uris) end def test_crl_uris # Multiple DistributionPoint contains a single general name each ef = OpenSSL::X509::ExtensionFactory.new ef.config = OpenSSL::Config.parse(<<~_cnf_) [crlDistPts] URI.1 = http://www.example.com/crl URI.2 = ldap://ldap.example.com/cn=ca?certificateRevocationList;binary _cnf_ cdp_cert = generate_cert(@ee1, @rsa1, 3, nil) ef.subject_certificate = cdp_cert cdp_cert.add_extension(ef.create_extension("crlDistributionPoints", "@crlDistPts")) cdp_cert.sign(@rsa1, "sha256") assert_equal( ["http://www.example.com/crl", "ldap://ldap.example.com/cn=ca?certificateRevocationList;binary"], cdp_cert.crl_uris ) end def test_crl_uris_multiple_general_names # Single DistributionPoint contains multiple general names of type URI ef = OpenSSL::X509::ExtensionFactory.new ef.config = OpenSSL::Config.parse(<<~_cnf_) [crlDistPts_section] fullname = URI:http://www.example.com/crl, URI:ldap://ldap.example.com/cn=ca?certificateRevocationList;binary _cnf_ cdp_cert = generate_cert(@ee1, @rsa1, 3, nil) ef.subject_certificate = cdp_cert cdp_cert.add_extension(ef.create_extension("crlDistributionPoints", "crlDistPts_section")) cdp_cert.sign(@rsa1, "sha256") assert_equal( ["http://www.example.com/crl", "ldap://ldap.example.com/cn=ca?certificateRevocationList;binary"], cdp_cert.crl_uris ) end def test_crl_uris_no_uris # The only DistributionPointName is a directoryName ef = OpenSSL::X509::ExtensionFactory.new ef.config = OpenSSL::Config.parse(<<~_cnf_) [crlDistPts_section] fullname = dirName:dirname_section [dirname_section] CN = dirname _cnf_ cdp_cert = generate_cert(@ee1, @rsa1, 3, nil) ef.subject_certificate = cdp_cert cdp_cert.add_extension(ef.create_extension("crlDistributionPoints", "crlDistPts_section")) cdp_cert.sign(@rsa1, "sha256") assert_nil(cdp_cert.crl_uris) end def test_aia_missing cert = issue_cert(@ee1, @rsa1, 1, [], nil, nil) assert_nil(cert.ca_issuer_uris) assert_nil(cert.ocsp_uris) end def test_aia ef = OpenSSL::X509::ExtensionFactory.new aia_cert = generate_cert(@ee1, @rsa1, 4, nil) ef.subject_certificate = aia_cert aia_cert.add_extension( ef.create_extension( "authorityInfoAccess", "caIssuers;URI:http://www.example.com/caIssuers," \ "caIssuers;URI:ldap://ldap.example.com/cn=ca?authorityInfoAccessCaIssuers;binary," \ "OCSP;URI:http://www.example.com/ocsp," \ "OCSP;URI:ldap://ldap.example.com/cn=ca?authorityInfoAccessOcsp;binary", false ) ) aia_cert.sign(@rsa1, "sha256") assert_equal( ["http://www.example.com/caIssuers", "ldap://ldap.example.com/cn=ca?authorityInfoAccessCaIssuers;binary"], aia_cert.ca_issuer_uris ) assert_equal( ["http://www.example.com/ocsp", "ldap://ldap.example.com/cn=ca?authorityInfoAccessOcsp;binary"], aia_cert.ocsp_uris ) end def test_invalid_extension integer = OpenSSL::ASN1::Integer.new(0) invalid_exts_cert = generate_cert(@ee1, @rsa1, 1, nil) ["subjectKeyIdentifier", "authorityKeyIdentifier", "crlDistributionPoints", "authorityInfoAccess"].each do |ext| invalid_exts_cert.add_extension( OpenSSL::X509::Extension.new(ext, integer.to_der) ) end assert_raise(OpenSSL::ASN1::ASN1Error, "invalid extension") { invalid_exts_cert.authority_key_identifier } assert_raise(OpenSSL::ASN1::ASN1Error, "invalid extension") { invalid_exts_cert.subject_key_identifier } assert_raise(OpenSSL::ASN1::ASN1Error, "invalid extension") { invalid_exts_cert.crl_uris } assert_raise(OpenSSL::ASN1::ASN1Error, "invalid extension") { invalid_exts_cert.ca_issuer_uris } assert_raise(OpenSSL::ASN1::ASN1Error, "invalid extension") { invalid_exts_cert.ocsp_uris } end def test_sign_and_verify cert = issue_cert(@ca, @rsa1, 1, [], nil, nil, digest: "SHA256") assert_equal("sha256WithRSAEncryption", cert.signature_algorithm) # ln assert_equal(true, cert.verify(@rsa1)) assert_equal(false, cert.verify(@rsa2)) assert_equal(false, certificate_error_returns_false { cert.verify(@ec1) }) cert.serial = 2 assert_equal(false, cert.verify(@rsa1)) end def test_sign_and_verify_nil_digest # Ed25519 is not FIPS-approved. omit_on_fips ed25519 = OpenSSL::PKey::generate_key("ED25519") cert = issue_cert(@ca, ed25519, 1, [], nil, nil, digest: nil) assert_equal(true, cert.verify(ed25519)) end def test_check_private_key cert = issue_cert(@ca, @rsa1, 1, [], nil, nil) assert_equal(true, cert.check_private_key(@rsa1)) end def test_read_from_file cert = issue_cert(@ca, @rsa1, 1, [], nil, nil) Tempfile.create("cert") { |f| f << cert.to_pem f.rewind assert_equal cert.to_der, OpenSSL::X509::Certificate.new(f).to_der } end def test_read_der_then_pem cert1 = issue_cert(@ca, @rsa1, 1, [], nil, nil) exts = [ # A new line before PEM block ["nsComment", "Another certificate:\n" + cert1.to_pem], ] cert2 = issue_cert(@ca, @rsa1, 2, exts, nil, nil) assert_equal cert2, OpenSSL::X509::Certificate.new(cert2.to_der) assert_equal cert2, OpenSSL::X509::Certificate.new(cert2.to_pem) end def test_eq now = Time.now cacert = issue_cert(@ca, @rsa1, 1, [], nil, nil, not_before: now, not_after: now + 3600) cert1 = issue_cert(@ee1, @rsa2, 2, [], cacert, @rsa1, not_before: now, not_after: now + 3600) cert2 = issue_cert(@ee1, @rsa2, 2, [], cacert, @rsa1, not_before: now, not_after: now + 3600) cert3 = issue_cert(@ee1, @rsa2, 3, [], cacert, @rsa1, not_before: now, not_after: now + 3600) cert4 = issue_cert(@ee1, @rsa2, 2, [], cacert, @rsa1, digest: "sha512", not_before: now, not_after: now + 3600) assert_equal false, cert1 == 12345 assert_equal true, cert1 == cert2 assert_equal false, cert1 == cert3 assert_equal false, cert1 == cert4 assert_equal false, cert3 == cert4 end def test_inspect cacert = issue_cert(@ca, @rsa1, 1, [], nil, nil) assert_include(cacert.inspect, "subject=#{@ca.inspect}") # Do not raise an exception for an invalid certificate assert_instance_of(String, OpenSSL::X509::Certificate.new.inspect) end def test_marshal now = Time.now cacert = issue_cert(@ca, @rsa1, 1, [], nil, nil, not_before: now, not_after: now + 3600) cert = issue_cert(@ee1, @rsa2, 2, [], cacert, @rsa1, not_before: now, not_after: now + 3600) deserialized = Marshal.load(Marshal.dump(cert)) assert_equal cert.to_der, deserialized.to_der end def test_load_file_empty_pem Tempfile.create("empty.pem") do |f| f.close assert_raise(OpenSSL::X509::CertificateError) do OpenSSL::X509::Certificate.load_file(f.path) end end end def test_load_file_fullchain_pem cert1 = issue_cert(@ee1, @rsa1, 1, [], nil, nil) cert2 = issue_cert(@ca, @rsa2, 1, [], nil, nil) Tempfile.create("fullchain.pem") do |f| f.puts cert1.to_pem f.puts cert2.to_pem f.close certificates = OpenSSL::X509::Certificate.load_file(f.path) assert_equal 2, certificates.size assert_equal @ee1, certificates[0].subject assert_equal @ca, certificates[1].subject end end def test_load_file_certificate_der cert = issue_cert(@ca, @rsa1, 1, [], nil, nil) Tempfile.create("certificate.der", binmode: true) do |f| f.write cert.to_der f.close certificates = OpenSSL::X509::Certificate.load_file(f.path) # DER encoding can only contain one certificate: assert_equal 1, certificates.size assert_equal cert.to_der, certificates[0].to_der end end def test_load_file_fullchain_garbage Tempfile.create("garbage.txt") do |f| f.puts "not a certificate" f.close assert_raise(OpenSSL::X509::CertificateError) do OpenSSL::X509::Certificate.load_file(f.path) end end end def test_tbs_precert_bytes cert = issue_cert(@ca, @rsa1, 1, [], nil, nil) seq = OpenSSL::ASN1.decode(cert.tbs_bytes) assert_equal 7, seq.value.size end private def certificate_error_returns_false yield rescue OpenSSL::X509::CertificateError false end end end ================================================ FILE: test/openssl/test_x509crl.rb ================================================ # frozen_string_literal: true require_relative "utils" if defined?(OpenSSL) class OpenSSL::TestX509CRL < OpenSSL::TestCase def setup super @rsa1 = Fixtures.pkey("rsa-1") @rsa2 = Fixtures.pkey("rsa-2") @ca = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=CA") end def test_basic now = Time.at(Time.now.to_i) cert = issue_cert(@ca, @rsa1, 1, [], nil, nil) crl = issue_crl([], 1, now, now+1600, [], cert, @rsa1, "SHA256") 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) assert_equal("sha256WithRSAEncryption", crl.signature_algorithm) # ln 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, @rsa1, 1, [], nil, nil) crl = issue_crl(revoke_info, 1, Time.now, Time.now+1600, [], cert, @rsa1, "SHA256") 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, @rsa1, "SHA256") 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@ruby-lang.org", false], ["keyUsage", "cRLSign, keyCertSign", true], ] crl_exts = [ ["authorityKeyIdentifier", "issuer:always,keyid:always", false], ["issuerAltName", "issuer:copy", false], ] cert = issue_cert(@ca, @rsa1, 1, cert_exts, nil, nil) crl = issue_crl([], 1, Time.now, Time.now+1600, crl_exts, cert, @rsa1, "SHA256") 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?) expected_keyid = OpenSSL::TestUtils.get_subject_key_id(cert, hex: false) assert_equal expected_keyid, crl.authority_key_identifier 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@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@ruby-lang.org", exts[2].value) assert_equal(false, exts[2].critical?) no_ext_crl = issue_crl([], 1, Time.now, Time.now+1600, [], cert, @rsa1, "SHA256") assert_equal nil, no_ext_crl.authority_key_identifier end def test_crlnumber cert = issue_cert(@ca, @rsa1, 1, [], nil, nil) crl = issue_crl([], 1, Time.now, Time.now+1600, [], cert, @rsa1, "SHA256") 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, @rsa1, "SHA256") 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, @rsa1, "SHA256") 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 p256 = Fixtures.pkey("p256") cert = issue_cert(@ca, @rsa1, 1, [], nil, nil) crl = issue_crl([], 1, Time.now, Time.now+1600, [], cert, @rsa1, "SHA256") assert_equal(true, crl.verify(@rsa1)) assert_equal(false, crl.verify(@rsa2)) assert_equal(false, crl_error_returns_false { crl.verify(p256) }) crl.version = 0 assert_equal(false, crl.verify(@rsa1)) cert = issue_cert(@ca, p256, 1, [], nil, nil) crl = issue_crl([], 1, Time.now, Time.now+1600, [], cert, p256, "SHA256") assert_equal(false, crl_error_returns_false { crl.verify(@rsa1) }) assert_equal(false, crl_error_returns_false { crl.verify(@rsa2) }) assert_equal(true, crl.verify(p256)) crl.version = 0 assert_equal(false, crl.verify(p256)) end def test_sign_and_verify_nil_digest # Ed25519 is not FIPS-approved. omit_on_fips ed25519 = OpenSSL::PKey::generate_key("ED25519") cert = issue_cert(@ca, ed25519, 1, [], nil, nil, digest: nil) crl = issue_crl([], 1, Time.now, Time.now+1600, [], cert, ed25519, nil) assert_equal(false, crl_error_returns_false { crl.verify(@rsa1) }) assert_equal(false, crl.verify(OpenSSL::PKey::generate_key("ED25519"))) assert_equal(true, crl.verify(ed25519)) crl.version = 0 assert_equal(false, crl.verify(ed25519)) end def test_revoked_to_der # revokedCertificates SEQUENCE OF SEQUENCE { # userCertificate CertificateSerialNumber, # revocationDate Time, # crlEntryExtensions Extensions OPTIONAL # -- if present, version MUST be v2 # } OPTIONAL, now = Time.utc(2000, 1, 1) rev1 = OpenSSL::X509::Revoked.new rev1.serial = 123 rev1.time = now ext = OpenSSL::X509::Extension.new("CRLReason", OpenSSL::ASN1::Enumerated(1)) rev1.extensions = [ext] asn1 = OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Integer(123), OpenSSL::ASN1::UTCTime(now), OpenSSL::ASN1::Sequence([ext.to_der]) ]) assert_equal asn1.to_der, rev1.to_der end def test_eq now = Time.now cacert = issue_cert(@ca, @rsa1, 1, [], nil, nil) crl1 = issue_crl([], 1, now, now + 3600, [], cacert, @rsa1, "SHA256") rev1 = OpenSSL::X509::Revoked.new.tap { |rev| rev.serial = 1 rev.time = now } crl1.add_revoked(rev1) crl2 = OpenSSL::X509::CRL.new(crl1.to_der) # CRL assert_equal false, crl1 == 12345 assert_equal true, crl1 == crl2 rev2 = OpenSSL::X509::Revoked.new.tap { |rev| rev.serial = 2 rev.time = now } crl2.add_revoked(rev2) assert_equal false, crl1 == crl2 # Revoked assert_equal false, rev1 == 12345 assert_equal true, rev1 == crl2.revoked[0] assert_equal false, rev1 == crl2.revoked[1] assert_equal true, rev2 == crl2.revoked[1] end def test_marshal now = Time.now cacert = issue_cert(@ca, @rsa1, 1, [], nil, nil) crl = issue_crl([], 1, now, now + 3600, [], cacert, @rsa1, "SHA256") rev = OpenSSL::X509::Revoked.new.tap { |rev| rev.serial = 1 rev.time = now } crl.add_revoked(rev) deserialized = Marshal.load(Marshal.dump(crl)) assert_equal crl.to_der, deserialized.to_der assert_equal crl.revoked[0].to_der, deserialized.revoked[0].to_der end private def crl_error_returns_false yield rescue OpenSSL::X509::CRLError false end end end ================================================ FILE: test/openssl/test_x509ext.rb ================================================ # frozen_string_literal: true require_relative 'utils' if defined?(OpenSSL) 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_include(cdp.value, "URI:http://www.example.com/crl") assert_include(cdp.value, "URI:ldap://ldap.example.com/cn=ca?certificateRevocationList;binary") cdp = ef.create_extension("crlDistributionPoints", "critical, @crlDistPts") assert_equal(true, cdp.critical?) assert_equal("crlDistributionPoints", cdp.oid) assert_include(cdp.value, "URI:http://www.example.com/crl") assert_include(cdp.value, "URI:ldap://ldap.example.com/cn=ca?certificateRevocationList;binary") cp = ef.create_extension("certificatePolicies", "@certPolicies") assert_equal(false, cp.critical?) assert_equal("certificatePolicies", cp.oid) assert_include(cp.value, "2.23.140.1.2.1") assert_include(cp.value, "http://cps.example.com") end def test_factory_create_extension_sn_ln ef = OpenSSL::X509::ExtensionFactory.new bc_sn = ef.create_extension("basicConstraints", "critical, CA:TRUE, pathlen:2") bc_ln = ef.create_extension("X509v3 Basic Constraints", "critical, CA:TRUE, pathlen:2") assert_equal(@basic_constraints.to_der, bc_sn.to_der) assert_equal(@basic_constraints.to_der, bc_ln.to_der) end def test_factory_create_extension_oid ef = OpenSSL::X509::ExtensionFactory.new ef.config = OpenSSL::Config.parse(<<~_end_of_cnf_) [basic_constraints] cA = BOOLEAN:TRUE pathLenConstraint = INTEGER:2 _end_of_cnf_ bc_oid = ef.create_extension("2.5.29.19", "ASN1:SEQUENCE:basic_constraints", true) assert_equal(@basic_constraints.to_der, bc_oid.to_der) 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 def test_eq ext1 = OpenSSL::X509::Extension.new(@basic_constraints.to_der) ef = OpenSSL::X509::ExtensionFactory.new ext2 = ef.create_extension("basicConstraints", "critical, CA:TRUE, pathlen:2") ext3 = ef.create_extension("basicConstraints", "critical, CA:TRUE") assert_equal false, ext1 == 12345 assert_equal true, ext1 == ext2 assert_equal false, ext1 == ext3 end def test_marshal ef = OpenSSL::X509::ExtensionFactory.new ext = ef.create_extension("basicConstraints", "critical, CA:TRUE, pathlen:2") deserialized = Marshal.load(Marshal.dump(ext)) assert_equal ext.to_der, deserialized.to_der end def test_value_der ext = OpenSSL::X509::Extension.new(@basic_constraints.to_der) assert_equal @basic_constraints_value.to_der, ext.value_der end end end ================================================ FILE: test/openssl/test_x509name.rb ================================================ # coding: ASCII-8BIT # frozen_string_literal: true require_relative 'utils' if defined?(OpenSSL) 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@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@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@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/1.2.3.4.5.6=A=BCD" name = OpenSSL::X509::Name.parse(dn) assert_equal(dn, name.to_s) ary = name.to_a assert_equal [ ["DC", "org", OpenSSL::ASN1::IA5STRING], ["DC", "ruby-lang", OpenSSL::ASN1::IA5STRING], ["CN", "www.ruby-lang.org", OpenSSL::ASN1::UTF8STRING], ["1.2.3.4.5.6", "A=BCD", OpenSSL::ASN1::UTF8STRING], ], ary dn2 = "DC=org, DC=ruby-lang, CN=www.ruby-lang.org, 1.2.3.4.5.6=A=BCD" name = OpenSSL::X509::Name.parse(dn2) assert_equal(dn, name.to_s) assert_equal ary, name.to_a 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]) assert_equal(OpenSSL::ASN1::PRINTABLESTRING, ary[3][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@ruby-lang.org"], ], scanner.call( "emailAddress=gotoyuzo@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@ruby-lang.org"], ["emailAddress", "gotoyuzo@ruby-lang.org"], ], scanner.call( "emailAddress=gotoyuzo@ruby-lang.org," + "1.2.840.113549.1.9.1=gotoyuzo@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@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@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@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 # openssl/crypto/objects/obj_mac.h 1.83 dn = [ ["DC", "org"], ["DC", "ruby-lang"], ["CN", "GOTOU Yuuzou"], ["emailAddress", "gotoyuzo@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@ruby-lang.org/serialNumber=123/street=Namiki", name.to_s) assert_equal("Namiki", ary[5][1]) end def test_add_entry_placing der = %w{ 30 2A 31 12 30 10 06 03 55 04 0A 0C 09 72 75 62 79 2D 6C 61 6E 67 31 14 30 08 06 03 55 04 0B 0C 01 61 30 08 06 03 55 04 0B 0C 01 62 } orig = OpenSSL::X509::Name.new([der.join].pack("H*")) assert_equal("OU=b+OU=a,O=ruby-lang", orig.to_s(OpenSSL::X509::Name::RFC2253)) # Skip for now; they do not work # # dn = orig.dup # dn.add_entry("CN", "unya", loc: 0, set: 0) # assert_equal("OU=b+OU=a,O=ruby-lang,CN=unya", dn.dup.to_s(OpenSSL::X509::Name::RFC2253)) # dn = orig.dup # dn.add_entry("CN", "unya", loc: 0, set: 1) # assert_equal("OU=b+OU=a,O=ruby-lang+CN=unya", dn.dup.to_s(OpenSSL::X509::Name::RFC2253)) dn = orig.dup dn.add_entry("CN", "unya", loc: 1, set: -1) assert_equal("OU=b+OU=a,O=ruby-lang+CN=unya", dn.dup.to_s(OpenSSL::X509::Name::RFC2253)) # dn = orig.dup # dn.add_entry("CN", "unya", loc: 1, set: 0) # assert_equal("OU=b+OU=a,CN=unya,O=ruby-lang", dn.dup.to_s(OpenSSL::X509::Name::RFC2253)) dn = orig.dup dn.add_entry("CN", "unya", loc: 1, set: 1) assert_equal("CN=unya+OU=b+OU=a,O=ruby-lang", dn.dup.to_s(OpenSSL::X509::Name::RFC2253)) dn = orig.dup dn.add_entry("CN", "unya", loc: -1, set: -1) assert_equal("CN=unya+OU=b+OU=a,O=ruby-lang", dn.dup.to_s(OpenSSL::X509::Name::RFC2253)) dn = orig.dup dn.add_entry("CN", "unya", loc: -1, set: 0) assert_equal("CN=unya,OU=b+OU=a,O=ruby-lang", dn.dup.to_s(OpenSSL::X509::Name::RFC2253)) end def test_to_s dn = [ ["DC", "org"], ["DC", "ruby-lang"], ["CN", "フー, バー"], ] name = OpenSSL::X509::Name.new dn.each { |x| name.add_entry(*x) } assert_equal "/DC=org/DC=ruby-lang/" \ "CN=\\xE3\\x83\\x95\\xE3\\x83\\xBC, \\xE3\\x83\\x90\\xE3\\x83\\xBC", name.to_s # OpenSSL escapes characters with MSB by default assert_equal \ "CN=\\E3\\83\\95\\E3\\83\\BC\\, \\E3\\83\\90\\E3\\83\\BC," \ "DC=ruby-lang,DC=org", name.to_s(OpenSSL::X509::Name::RFC2253) assert_equal "DC = org, DC = ruby-lang, " \ "CN = \"\\E3\\83\\95\\E3\\83\\BC, \\E3\\83\\90\\E3\\83\\BC\"", name.to_s(OpenSSL::X509::Name::ONELINE) empty = OpenSSL::X509::Name.new assert_equal "", empty.to_s assert_equal "", empty.to_s(OpenSSL::X509::Name::COMPAT) assert_equal "", empty.to_s(OpenSSL::X509::Name::RFC2253) assert_equal "", empty.to_s(OpenSSL::X509::Name::ONELINE) end def test_to_utf8 dn = [ ["DC", "org"], ["DC", "ruby-lang"], ["CN", "フー, バー"], ] name = OpenSSL::X509::Name.new dn.each { |x| name.add_entry(*x) } str = name.to_utf8 expected = String.new("CN=フー\\, バー,DC=ruby-lang,DC=org").force_encoding("UTF-8") assert_equal expected, str assert_equal Encoding.find("UTF-8"), str.encoding empty = OpenSSL::X509::Name.new assert_equal "", empty.to_utf8 end def test_equals2 n1 = OpenSSL::X509::Name.parse_rfc2253 'CN=a' n2 = OpenSSL::X509::Name.parse_rfc2253 'CN=a' assert_equal n1, n2 assert_equal(false, n1 == 'abc') assert_equal(false, n2 == nil) end def test_spaceship n1 = OpenSSL::X509::Name.new([["CN", "a"]]) n2 = OpenSSL::X509::Name.new([["CN", "a"]]) n3 = OpenSSL::X509::Name.new([["CN", "ab"]]) assert_equal(0, n1 <=> n2) assert_equal(-1, n1 <=> n3) assert_equal(0, n2 <=> n1) assert_equal(-1, n2 <=> n3) assert_equal(1, n3 <=> n1) assert_equal(1, n3 <=> n2) assert_equal(nil, n1 <=> 'abc') assert_equal(nil, n2 <=> 123) assert_equal(nil, n3 <=> nil) end def test_hash_old omit_on_fips # MD5 dn = "/DC=org/DC=ruby-lang/CN=www.ruby-lang.org" name = OpenSSL::X509::Name.parse(dn) d = OpenSSL::Digest.digest('MD5', 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_old) 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_marshal name = OpenSSL::X509::Name.new([["DC", "org"], ["DC", "ruby-lang"], ["CN", "bar.ruby-lang.org"]]) deserialized = Marshal.load(Marshal.dump(name)) assert_equal name.to_der, deserialized.to_der 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 ================================================ FILE: test/openssl/test_x509req.rb ================================================ # frozen_string_literal: true require_relative "utils" if defined?(OpenSSL) class OpenSSL::TestX509Request < OpenSSL::TestCase def setup super @rsa1 = Fixtures.pkey("rsa-1") @rsa2 = Fixtures.pkey("rsa-2") @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 req.sign(key, digest) req end def test_public_key req = issue_csr(0, @dn, @rsa1, "SHA256") assert_kind_of(OpenSSL::PKey::RSA, req.public_key) assert_equal(@rsa1.public_to_der, req.public_key.public_to_der) req = OpenSSL::X509::Request.new(req.to_der) assert_equal(@rsa1.public_to_der, req.public_key.public_to_der) end def test_version req = issue_csr(0, @dn, @rsa1, "SHA256") assert_equal(0, req.version) req = OpenSSL::X509::Request.new(req.to_der) assert_equal(0, req.version) end def test_subject req = issue_csr(0, @dn, @rsa1, "SHA256") 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 test_signature_algorithm req = issue_csr(0, @dn, @rsa1, "SHA256") assert_equal("sha256WithRSAEncryption", req.signature_algorithm) # ln 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@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, @rsa1, "SHA256") attrs.each{|attr| req0.add_attribute(attr) } req1 = issue_csr(0, @dn, @rsa1, "SHA256") 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_digest_instance req1 = issue_csr(0, @dn, @rsa1, "SHA256") req2 = issue_csr(0, @dn, @rsa1, OpenSSL::Digest.new("SHA256")) assert_equal(req1.to_der, req2.to_der) end def test_sign_and_verify req = issue_csr(0, @dn, @rsa1, "SHA256") assert_equal(true, req.verify(@rsa1)) assert_equal(false, req.verify(@rsa2)) ec = OpenSSL::PKey::EC.generate("prime256v1") assert_equal(false, request_error_returns_false { req.verify(ec) }) req.subject = OpenSSL::X509::Name.parse_rfc2253("CN=FooBarFooBar,C=JP") assert_equal(false, req.verify(@rsa1)) end def test_sign_and_verify_nil_digest # Ed25519 is not FIPS-approved. omit_on_fips ed25519 = OpenSSL::PKey::generate_key("ED25519") req = issue_csr(0, @dn, ed25519, nil) assert_equal(false, request_error_returns_false { req.verify(@rsa1) }) assert_equal(false, request_error_returns_false { req.verify(@rsa2) }) assert_equal(false, req.verify(OpenSSL::PKey::generate_key("ED25519"))) assert_equal(true, req.verify(ed25519)) req.public_key = @rsa1 assert_equal(false, req.verify(ed25519)) end def test_dup req = issue_csr(0, @dn, @rsa1, "SHA256") assert_equal(req.to_der, req.dup.to_der) end def test_eq req1 = issue_csr(0, @dn, @rsa1, "SHA256") req2 = issue_csr(0, @dn, @rsa1, "SHA256") req3 = issue_csr(0, @dn, @rsa1, "SHA512") assert_equal false, req1 == 12345 assert_equal true, req1 == req2 assert_equal false, req1 == req3 end def test_marshal req = issue_csr(0, @dn, @rsa1, "SHA256") deserialized = Marshal.load(Marshal.dump(req)) assert_equal req.to_der, deserialized.to_der end private def request_error_returns_false yield rescue OpenSSL::X509::RequestError false end end end ================================================ FILE: test/openssl/test_x509store.rb ================================================ # frozen_string_literal: true require_relative "utils" if defined?(OpenSSL) class OpenSSL::TestX509Store < OpenSSL::TestCase def test_store_new # v2.3.0 emits explicit warning assert_warning(/new does not take any arguments/) { OpenSSL::X509::Store.new(123) } end def test_add_file_path ca_exts = [ ["basicConstraints", "CA:TRUE", true], ["keyUsage", "cRLSign,keyCertSign", true], ] cert1_subj = OpenSSL::X509::Name.parse_rfc2253("CN=Cert 1") cert1_key = Fixtures.pkey("rsa-1") cert1 = issue_cert(cert1_subj, cert1_key, 1, ca_exts, nil, nil) cert2_subj = OpenSSL::X509::Name.parse_rfc2253("CN=Cert 2") cert2_key = Fixtures.pkey("rsa-2") cert2 = issue_cert(cert2_subj, cert2_key, 1, ca_exts, nil, nil) # X509::Store#add_file reads concatenated PEM file 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) # X509::Store#add_path Dir.mktmpdir do |dir| hash1 = "%08x.%d" % [cert1_subj.hash, 0] File.write(File.join(dir, hash1), cert1.to_pem) store = OpenSSL::X509::Store.new store.add_path(dir) assert_equal true, store.verify(cert1) assert_equal false, store.verify(cert2) end # OpenSSL < 1.1.1 leaks an error on a duplicate certificate assert_nothing_raised { store.add_file(tmpfile.path) } assert_equal [], OpenSSL.errors # Non-String is given assert_raise(TypeError) { store.add_file(nil) } ensure tmpfile and tmpfile.close! end def test_verify_simple ca_exts = [ ["basicConstraints", "CA:TRUE", true], ["keyUsage", "cRLSign,keyCertSign", true], ] ca1 = OpenSSL::X509::Name.parse_rfc2253("CN=Root CA") ca1_key = Fixtures.pkey("rsa-1") ca1_cert = issue_cert(ca1, ca1_key, 1, ca_exts, nil, nil) ca2 = OpenSSL::X509::Name.parse_rfc2253("CN=Intermediate CA") ca2_key = Fixtures.pkey("rsa-2") ca2_cert = issue_cert(ca2, ca2_key, 2, ca_exts, ca1_cert, ca1_key) ee_exts = [ ["keyUsage", "keyEncipherment,digitalSignature", true], ] ee1 = OpenSSL::X509::Name.parse_rfc2253("CN=EE 1") ee1_key = Fixtures.pkey("rsa-3") ee1_cert = issue_cert(ee1, ee1_key, 10, ee_exts, ca2_cert, ca2_key) # Nothing trusted store = OpenSSL::X509::Store.new assert_equal(false, store.verify(ee1_cert, [ca2_cert, ca1_cert])) assert_equal(OpenSSL::X509::V_ERR_SELF_SIGNED_CERT_IN_CHAIN, store.error) assert_match(/self.signed/i, store.error_string) # CA1 trusted, CA2 missing store = OpenSSL::X509::Store.new store.add_cert(ca1_cert) assert_equal(false, store.verify(ee1_cert)) assert_equal(OpenSSL::X509::V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, store.error) # CA1 trusted, CA2 supplied store = OpenSSL::X509::Store.new store.add_cert(ca1_cert) assert_equal(true, store.verify(ee1_cert, [ca2_cert])) assert_match(/ok/i, store.error_string) assert_equal(OpenSSL::X509::V_OK, store.error) assert_equal([ee1_cert, ca2_cert, ca1_cert], store.chain) # Manually instantiated StoreContext # Nothing trusted store = OpenSSL::X509::Store.new ctx = OpenSSL::X509::StoreContext.new(store, ee1_cert) assert_nil(ctx.current_cert) assert_nil(ctx.current_crl) assert_equal(false, ctx.verify) assert_equal(OpenSSL::X509::V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, ctx.error) assert_equal(0, ctx.error_depth) assert_equal([ee1_cert], ctx.chain) assert_equal(ee1_cert, ctx.current_cert) end def test_verify_callback ca_exts = [ ["basicConstraints", "CA:TRUE", true], ["keyUsage", "cRLSign,keyCertSign", true], ] ca1 = OpenSSL::X509::Name.parse_rfc2253("CN=Root CA") ca1_key = Fixtures.pkey("rsa-1") ca1_cert = issue_cert(ca1, ca1_key, 1, ca_exts, nil, nil) ca2 = OpenSSL::X509::Name.parse_rfc2253("CN=Intermediate CA") ca2_key = Fixtures.pkey("rsa-2") ca2_cert = issue_cert(ca2, ca2_key, 2, ca_exts, ca1_cert, ca1_key) ee_exts = [ ["keyUsage", "keyEncipherment,digitalSignature", true], ] ee1 = OpenSSL::X509::Name.parse_rfc2253("CN=EE 1") ee1_key = Fixtures.pkey("rsa-3") ee1_cert = issue_cert(ee1, ee1_key, 10, ee_exts, ca2_cert, ca2_key) # verify_callback on X509::Store is called with proper arguments cb_calls = [] store = OpenSSL::X509::Store.new store.verify_callback = -> (preverify_ok, sctx) { cb_calls << [preverify_ok, sctx.current_cert] preverify_ok } store.add_cert(ca1_cert) assert_equal(true, store.verify(ee1_cert, [ca2_cert])) assert_include([2, 3, 4, 5], cb_calls.size) cb_calls.each do |pre_ok, cert| assert_equal(true, pre_ok) assert_include([ca1_cert, ca2_cert, ee1_cert], cert) end # verify_callback can change verification result store = OpenSSL::X509::Store.new store.verify_callback = -> (preverify_ok, sctx) { next preverify_ok if sctx.current_cert != ee1_cert sctx.error = OpenSSL::X509::V_ERR_APPLICATION_VERIFICATION false } store.add_cert(ca1_cert) assert_equal(false, store.verify(ee1_cert, [ca2_cert])) assert_equal(OpenSSL::X509::V_ERR_APPLICATION_VERIFICATION, store.error) # Exception raised by verify_callback is currently suppressed, and is # treated as a non-truthy return (with warning) store = OpenSSL::X509::Store.new store.verify_callback = -> (preverify_ok, sctx) { raise "suppressed" } store.add_cert(ca1_cert) assert_warning(/exception in verify_callback/) { assert_equal(false, store.verify(ee1_cert, [ca2_cert])) } # The block given to X509::Store#verify replaces it called = nil store = OpenSSL::X509::Store.new store.verify_callback = -> (preverify_ok, sctx) { called = :store; preverify_ok } store.add_cert(ca1_cert) blk = proc { |preverify_ok, sctx| called = :block; preverify_ok } assert_equal(true, store.verify(ee1_cert, [ca2_cert], &blk)) assert_equal(:block, called) end def test_verify_purpose ca_exts = [ ["basicConstraints", "CA:TRUE", true], ["keyUsage", "cRLSign,keyCertSign", true], ] ca1 = OpenSSL::X509::Name.parse_rfc2253("CN=Root CA") ca1_key = Fixtures.pkey("rsa-1") ca1_cert = issue_cert(ca1, ca1_key, 1, ca_exts, nil, nil) ee_exts = [ ["keyUsage", "keyEncipherment,digitalSignature", true], ] ee1 = OpenSSL::X509::Name.parse_rfc2253("CN=EE 1") ee1_key = Fixtures.pkey("rsa-3") ee1_cert = issue_cert(ee1, ee1_key, 10, ee_exts, ca1_cert, ca1_key) # Purpose not set store = OpenSSL::X509::Store.new store.add_cert(ca1_cert) assert_equal(true, store.verify(ca1_cert)) assert_equal(true, store.verify(ee1_cert)) # Purpose set to X509::PURPOSE_SSL_CLIENT; keyUsage is checked store = OpenSSL::X509::Store.new store.purpose = OpenSSL::X509::PURPOSE_CRL_SIGN store.add_cert(ca1_cert) assert_equal(true, store.verify(ca1_cert)) assert_equal(false, store.verify(ee1_cert)) end def test_verify_validity_period # Creating test certificates with validity periods: # # now-5000 now-1000 now+1000 now+5000 # CA1:|---------------------------------------------------------------| # EE1:|---------------------------------------------------------------| # EE2:|-------------------------| # EE3: |-------------------------| now = Time.now ca_exts = [ ["basicConstraints", "CA:TRUE", true], ["keyUsage", "cRLSign,keyCertSign", true], ] ca1 = OpenSSL::X509::Name.parse_rfc2253("CN=Root CA") ca1_key = Fixtures.pkey("rsa-1") ca1_cert = issue_cert(ca1, ca1_key, 1, ca_exts, nil, nil, not_before: now - 5000, not_after: now + 5000) ee_exts = [ ["keyUsage", "keyEncipherment,digitalSignature", true], ] ee1 = OpenSSL::X509::Name.parse_rfc2253("CN=EE 1") ee1_key = Fixtures.pkey("rsa-1") ee1_cert = issue_cert(ee1, ee1_key, 11, ee_exts, ca1_cert, ca1_key, not_before: now - 5000, not_after: now + 5000) ee2 = OpenSSL::X509::Name.parse_rfc2253("CN=EE 2") ee2_key = Fixtures.pkey("rsa-2") ee2_cert = issue_cert(ee2, ee2_key, 12, ee_exts, ca1_cert, ca1_key, not_before: now - 5000, not_after: now - 1000) ee3 = OpenSSL::X509::Name.parse_rfc2253("CN=EE 3") ee3_key = Fixtures.pkey("rsa-3") ee3_cert = issue_cert(ee3, ee3_key, 13, ee_exts, ca1_cert, ca1_key, not_before: now + 1000, not_after: now + 5000) # Using system time store = OpenSSL::X509::Store.new store.add_cert(ca1_cert) assert_equal(true, store.verify(ee1_cert)) assert_equal(false, store.verify(ee2_cert)) assert_equal(OpenSSL::X509::V_ERR_CERT_HAS_EXPIRED, store.error) assert_equal(false, store.verify(ee3_cert)) assert_equal(OpenSSL::X509::V_ERR_CERT_NOT_YET_VALID, store.error) # Time set to now-2000; EE2 is still valid store = OpenSSL::X509::Store.new store.time = now - 2000 store.add_cert(ca1_cert) assert_equal(true, store.verify(ee1_cert)) assert_equal(true, store.verify(ee2_cert)) assert_equal(false, store.verify(ee3_cert)) assert_equal(OpenSSL::X509::V_ERR_CERT_NOT_YET_VALID, store.error) end def test_verify_with_crl ca_exts = [ ["basicConstraints", "CA:TRUE", true], ["keyUsage", "cRLSign,keyCertSign", true], ] ca1 = OpenSSL::X509::Name.parse_rfc2253("CN=Root CA") ca1_key = Fixtures.pkey("rsa-1") ca1_cert = issue_cert(ca1, ca1_key, 1, ca_exts, nil, nil) ca2 = OpenSSL::X509::Name.parse_rfc2253("CN=Intermediate CA") ca2_key = Fixtures.pkey("rsa-2") ca2_cert = issue_cert(ca2, ca2_key, 2, ca_exts, ca1_cert, ca1_key) ee_exts = [ ["keyUsage", "keyEncipherment,digitalSignature", true], ] ee1 = OpenSSL::X509::Name.parse_rfc2253("CN=EE 1") ee1_key = Fixtures.pkey("rsa-3") ee1_cert = issue_cert(ee1, ee1_key, 10, ee_exts, ca2_cert, ca2_key) ee2 = OpenSSL::X509::Name.parse_rfc2253("CN=EE 2") ee2_key = Fixtures.pkey("rsa-3") ee2_cert = issue_cert(ee2, ee2_key, 20, ee_exts, ca2_cert, ca2_key) # OpenSSL uses time(2) while Time.now uses clock_gettime(CLOCK_REALTIME), # and there may be difference, so giving 50 seconds margin. now = Time.now - 50 revoke_info = [] ca1_crl1 = issue_crl(revoke_info, 1, now, now+1800, [], ca1_cert, ca1_key, "sha256") revoke_info = [ [2, now, 1], ] ca1_crl2 = issue_crl(revoke_info, 2, now, now+1800, [], ca1_cert, ca1_key, "sha256") revoke_info = [ [20, now, 1], ] ca2_crl1 = issue_crl(revoke_info, 1, now, now+1800, [], ca2_cert, ca2_key, "sha256") revoke_info = [] ca2_crl2 = issue_crl(revoke_info, 2, now-100, now-1, [], ca2_cert, ca2_key, "sha256") # CRL check required, but no CRL supplied store = OpenSSL::X509::Store.new store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK store.add_cert(ca1_cert) assert_equal(false, store.verify(ca2_cert)) assert_equal(OpenSSL::X509::V_ERR_UNABLE_TO_GET_CRL, store.error) # Intermediate CA revoked EE2 store = OpenSSL::X509::Store.new store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK store.add_cert(ca1_cert) store.add_crl(ca1_crl1) # revoke no cert store.add_crl(ca2_crl1) # revoke ee2_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])) # Root CA revoked Intermediate CA; Intermediate CA revoked EE2 store = OpenSSL::X509::Store.new store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK store.add_cert(ca1_cert) store.add_crl(ca1_crl2) # revoke ca2_cert store.add_crl(ca2_crl1) # revoke ee2_cert assert_equal(false, store.verify(ca2_cert)) # Validity of intermediate CAs is not checked by default assert_equal(true, store.verify(ee1_cert, [ca2_cert])) assert_equal(false, store.verify(ee2_cert, [ca2_cert])) # Same as above, but with OpenSSL::X509::V_FLAG_CRL_CHECK_ALL store = OpenSSL::X509::Store.new store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK|OpenSSL::X509::V_FLAG_CRL_CHECK_ALL store.add_cert(ca1_cert) store.add_crl(ca1_crl2) # revoke ca2_cert store.add_crl(ca2_crl1) # revoke ee2_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])) # Expired CRL supplied store = OpenSSL::X509::Store.new 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(ca1_crl1) store.add_crl(ca2_crl2) # issued by ca2 but expired 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_add_cert_duplicate ca1 = OpenSSL::X509::Name.parse_rfc2253("CN=Root CA") ca1_key = Fixtures.pkey("rsa-1") ca1_cert = issue_cert(ca1, ca1_key, 1, [], nil, nil) store = OpenSSL::X509::Store.new store.add_cert(ca1_cert) assert_nothing_raised { store.add_cert(ca1_cert) # add same certificate twice } now = Time.now revoke_info = [] crl1 = issue_crl(revoke_info, 1, now, now+1800, [], ca1_cert, ca1_key, "sha256") revoke_info = [ [2, now, 1], ] crl2 = issue_crl(revoke_info, 2, now+1800, now+3600, [], ca1_cert, ca1_key, "sha256") store.add_crl(crl1) assert_nothing_raised { store.add_crl(crl2) # add CRL issued by same CA twice. } 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 def test_ctx_cleanup # Deprecated in Ruby 1.9.3 cert = OpenSSL::X509::Certificate.new store = OpenSSL::X509::Store.new ctx = OpenSSL::X509::StoreContext.new(store, cert, []) assert_warning(/cleanup/) { ctx.cleanup } end end end ================================================ FILE: test/openssl/ut_eof.rb ================================================ # frozen_string_literal: true require 'test/unit' if defined?(OpenSSL) module OpenSSL::TestEOF def test_getbyte_eof open_file("") {|f| assert_nil f.getbyte } end def test_readbyte_eof open_file("") {|f| assert_raise(EOFError) { f.readbyte } } end 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 end ================================================ FILE: test/openssl/utils.rb ================================================ # frozen_string_literal: true begin require "openssl" rescue LoadError end require "test/unit" require "core_assertions" require "tempfile" require "socket" if defined?(OpenSSL) module OpenSSL::TestUtils module Fixtures module_function def pkey(name) OpenSSL::PKey.read(read_file("pkey", name)) end def read_file(category, name) @file_cache ||= {} @file_cache[[category, name]] ||= File.read(File.join(__dir__, "fixtures", category, name + ".pem")) end end module_function def generate_cert(dn, key, serial, issuer, not_before: nil, not_after: nil) cert = OpenSSL::X509::Certificate.new issuer = cert unless issuer cert.version = 2 cert.serial = serial cert.subject = dn cert.issuer = issuer.subject cert.public_key = key now = Time.now cert.not_before = not_before || now - 3600 cert.not_after = not_after || now + 3600 cert end def issue_cert(dn, key, serial, extensions, issuer, issuer_key, not_before: nil, not_after: nil, digest: "sha256") cert = generate_cert(dn, key, serial, issuer, not_before: not_before, not_after: not_after) issuer = cert unless issuer issuer_key = key unless issuer_key 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)) } 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, hex: true) asn1_cert = OpenSSL::ASN1.decode(cert) tbscert = asn1_cert.value[0] pkinfo = tbscert.value[6] publickey = pkinfo.value[1] pkvalue = publickey.value digest = OpenSSL::Digest.digest('SHA1', pkvalue) if hex digest.unpack("H2"*20).join(":").upcase else digest end end def openssl?(major = nil, minor = nil, fix = nil, patch = 0, status = 0) return false if OpenSSL::OPENSSL_VERSION.include?("LibreSSL") || OpenSSL::OPENSSL_VERSION.include?("AWS-LC") return true unless major OpenSSL::OPENSSL_VERSION_NUMBER >= major * 0x10000000 + minor * 0x100000 + fix * 0x1000 + patch * 0x10 + status * 0x1 end def libressl?(major = nil, minor = nil, fix = nil) version = OpenSSL::OPENSSL_VERSION.scan(/LibreSSL (\d+)\.(\d+)\.(\d+).*/)[0] return false unless version !major || (version.map(&:to_i) <=> [major, minor, fix]) >= 0 end def aws_lc? OpenSSL::OPENSSL_VERSION.include?("AWS-LC") end end class OpenSSL::TestCase < Test::Unit::TestCase include OpenSSL::TestUtils extend OpenSSL::TestUtils include Test::Unit::CoreAssertions 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 # Omit the tests in FIPS. # # For example, the password based encryption used in the PEM format uses MD5 # for deriving the encryption key from the password, and MD5 is not # FIPS-approved. # # See https://github.com/openssl/openssl/discussions/21830#discussioncomment-6865636 # for details. def omit_on_fips return unless OpenSSL.fips_mode omit <<~MESSAGE Only for OpenSSL non-FIPS with the following possible reasons: * A testing logic is non-FIPS specific. * An encryption used in the test is not FIPS-approved. MESSAGE end def omit_on_non_fips return if OpenSSL.fips_mode omit "Only for OpenSSL FIPS" end end class OpenSSL::SSLTestCase < OpenSSL::TestCase RUBY = EnvUtil.rubybin ITERATIONS = ($0 == __FILE__) ? 100 : 10 def setup super @ca_key = Fixtures.pkey("rsa-1") @svr_key = Fixtures.pkey("rsa-2") @cli_key = Fixtures.pkey("rsa-3") @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 readwrite_loop(ctx, ssl) while line = ssl.gets ssl.write(line) end end def start_server(verify_mode: OpenSSL::SSL::VERIFY_NONE, ctx_proc: nil, server_proc: method(:readwrite_loop), accept_proc: proc{}, ignore_listener_error: false, &block) IO.pipe {|stop_pipe_r, stop_pipe_w| ctx = OpenSSL::SSL::SSLContext.new ctx.cert = @svr_cert ctx.key = @svr_key ctx.verify_mode = verify_mode ctx_proc.call(ctx) if ctx_proc Socket.do_not_reverse_lookup = true tcps = TCPServer.new("127.0.0.1", 0) port = tcps.connect_address.ip_port ssls = OpenSSL::SSL::SSLServer.new(tcps, ctx) threads = [] begin server_thread = Thread.new do Thread.current.report_on_exception = false begin loop do begin readable, = IO.select([ssls, stop_pipe_r]) break if readable.include? stop_pipe_r ssl = ssls.accept accept_proc.call(ssl) rescue OpenSSL::SSL::SSLError, IOError, Errno::EBADF, Errno::EINVAL, Errno::ECONNABORTED, Errno::ENOTSOCK, Errno::ECONNRESET retry if ignore_listener_error raise end th = Thread.new do Thread.current.report_on_exception = false begin server_proc.call(ctx, ssl) ensure ssl.close end true end threads << th end ensure tcps.close end end client_thread = Thread.new do Thread.current.report_on_exception = false begin block.call(port) ensure # Stop accepting new connection stop_pipe_w.close server_thread.join end end threads.unshift client_thread ensure # Terminate existing connections. If a thread did 'pend', re-raise it. pend = nil threads.each { |th| begin timeout = EnvUtil.apply_timeout_scale(30) th.join(timeout) or th.raise(RuntimeError, "[start_server] thread did not exit in #{timeout} secs") rescue Test::Unit::PendedError pend = $! rescue Exception end } raise pend if pend 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 assert_sign_verify_false_or_error ret = yield rescue => e assert_kind_of(OpenSSL::PKey::PKeyError, e) else assert_equal(false, ret) end def der_to_pem(der, pem_header) # RFC 7468 <<~EOS -----BEGIN #{pem_header}----- #{[der].pack("m0").scan(/.{1,64}/).join("\n")} -----END #{pem_header}----- EOS end def der_to_encrypted_pem(der, pem_header, password) # OpenSSL encryption, non-standard iv = 16.times.to_a.pack("C*") encrypted = OpenSSL::Cipher.new("aes-128-cbc").encrypt.then { |cipher| cipher.key = OpenSSL::Digest.digest("MD5", password + iv[0, 8]) cipher.iv = iv cipher.update(der) << cipher.final } <<~EOS -----BEGIN #{pem_header}----- Proc-Type: 4,ENCRYPTED DEK-Info: AES-128-CBC,#{iv.unpack1("H*").upcase} #{[encrypted].pack("m0").scan(/.{1,64}/).join("\n")} -----END #{pem_header}----- EOS end end module OpenSSL::Certs include OpenSSL::TestUtils module_function def ca_cert ca = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=Timestamp Root CA") ca_exts = [ ["basicConstraints","CA:TRUE,pathlen:1",true], ["keyUsage","keyCertSign, cRLSign",true], ["subjectKeyIdentifier","hash",false], ["authorityKeyIdentifier","keyid:always",false], ] OpenSSL::TestUtils.issue_cert(ca, Fixtures.pkey("rsa2048"), 1, ca_exts, nil, nil) end def ts_cert_direct(key, ca_cert) dn = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/OU=Timestamp/CN=Server Direct") exts = [ ["basicConstraints","CA:FALSE",true], ["keyUsage","digitalSignature, nonRepudiation", true], ["subjectKeyIdentifier", "hash",false], ["authorityKeyIdentifier","keyid,issuer", false], ["extendedKeyUsage", "timeStamping", true] ] OpenSSL::TestUtils.issue_cert(dn, key, 2, exts, ca_cert, Fixtures.pkey("rsa2048")) end def intermediate_cert(key, ca_cert) dn = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/OU=Timestamp/CN=Timestamp Intermediate CA") exts = [ ["basicConstraints","CA:TRUE,pathlen:0",true], ["keyUsage","keyCertSign, cRLSign",true], ["subjectKeyIdentifier","hash",false], ["authorityKeyIdentifier","keyid:always",false], ] OpenSSL::TestUtils.issue_cert(dn, key, 3, exts, ca_cert, Fixtures.pkey("rsa2048")) end def ts_cert_ee(key, intermediate, im_key) dn = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/OU=Timestamp/CN=Server End Entity") exts = [ ["keyUsage","digitalSignature, nonRepudiation", true], ["subjectKeyIdentifier", "hash",false], ["authorityKeyIdentifier","keyid,issuer", false], ["extendedKeyUsage", "timeStamping", true] ] OpenSSL::TestUtils.issue_cert(dn, key, 4, exts, intermediate, im_key) end end end ================================================ FILE: tool/openssl_fips.cnf.tmpl ================================================ config_diagnostics = 1 openssl_conf = openssl_init # It seems that the .include needs an absolute path. .include OPENSSL_DIR/ssl/fipsmodule.cnf [openssl_init] providers = provider_sect alg_section = algorithm_sect [provider_sect] fips = fips_sect base = base_sect [base_sect] activate = 1 [algorithm_sect] default_properties = fips=yes